From c35d16205d3567ae36d236c2a3d48aad80e5f8fa Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 12 Nov 2021 15:18:31 +1100 Subject: [PATCH 001/294] create a schema graph --- .../java/graphql/schema/diffing/Edge.java | 43 +++ .../graphql/schema/diffing/SchemaDiffing.java | 309 ++++++++++++++++++ .../graphql/schema/diffing/SchemaGraph.java | 62 ++++ .../java/graphql/schema/diffing/Vertex.java | 25 ++ .../schema/diffing/SchemaDiffingTest.groovy | 25 ++ 5 files changed, 464 insertions(+) create mode 100644 src/main/java/graphql/schema/diffing/Edge.java create mode 100644 src/main/java/graphql/schema/diffing/SchemaDiffing.java create mode 100644 src/main/java/graphql/schema/diffing/SchemaGraph.java create mode 100644 src/main/java/graphql/schema/diffing/Vertex.java create mode 100644 src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java new file mode 100644 index 0000000000..c6987fc36e --- /dev/null +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -0,0 +1,43 @@ +package graphql.schema.diffing; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class Edge { + private Vertex from; + private Vertex to; + + private Map properties = new LinkedHashMap<>(); + + public Edge(Vertex from, Vertex to) { + this.from = from; + this.to = to; + } + + public Vertex getFrom() { + return from; + } + + public void setFrom(Vertex from) { + this.from = from; + } + + public Vertex getTo() { + return to; + } + + public void setTo(Vertex to) { + this.to = to; + } + + + public void add(String propName, Object propValue) { + properties.put(propName, propValue); + } + + public T get(String propName) { + return (T) properties.get(propName); + } + + +} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java new file mode 100644 index 0000000000..b4df204ce5 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -0,0 +1,309 @@ +package graphql.schema.diffing; + +import graphql.schema.*; +import graphql.util.*; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static graphql.Assert.assertNotNull; + +public class SchemaDiffing { + + public static void diff(GraphQLSchema from, GraphQLSchema to) { + } + + public SchemaGraph createGraph(GraphQLSchema schema) { + Set roots = new LinkedHashSet<>(); + roots.add(schema.getQueryType()); + if (schema.isSupportingMutations()) { + roots.add(schema.getMutationType()); + } + if (schema.isSupportingSubscriptions()) { + roots.add(schema.getSubscriptionType()); + } + roots.addAll(schema.getAdditionalTypes()); + roots.addAll(schema.getDirectives()); + roots.addAll(schema.getSchemaDirectives()); + roots.add(schema.getIntrospectionSchemaType()); + Traverser traverser = Traverser.depthFirst(GraphQLSchemaElement::getChildren); + SchemaGraph schemaGraph = new SchemaGraph(); + traverser.traverse(roots, new TraverserVisitor() { + @Override + public TraversalControl enter(TraverserContext context) { + if (context.thisNode() instanceof GraphQLObjectType) { + newObject((GraphQLObjectType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLInterfaceType) { + newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLUnionType) { + newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLScalarType) { + newScalar((GraphQLScalarType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLInputObjectType) { + newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLEnumType) { + newEnum((GraphQLEnumType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLDirective) { + newDirective((GraphQLDirective) context.thisNode(), schemaGraph); + } + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl leave(TraverserContext context) { + return TraversalControl.CONTINUE; + } + }); + + for (Vertex vertex : schemaGraph.getVertices()) { + if ("Object".equals(vertex.getType())) { + handleObjectVertex(vertex, schemaGraph, schema); + } + if ("Interface".equals(vertex.getType())) { + handleInterfaceVertex(vertex, schemaGraph, schema); + } + if ("Union".equals(vertex.getType())) { + handleUnion(vertex, schemaGraph, schema); + } + if ("InputObject".equals(vertex.getType())) { + handleInputObject(vertex, schemaGraph, schema); + } + if ("AppliedDirective".equals(vertex.getType())) { + handleAppliedDirective(vertex, schemaGraph, schema); + } + } + return schemaGraph; + } + + private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); + schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); + } + + private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) graphQLSchema.getType(inputObject.get("name")); + List inputFields = inputObjectType.getFields(); + for (GraphQLInputObjectField inputField : inputFields) { + Vertex inputFieldVertex = schemaGraph.findTargetVertex(inputObject, vertex -> vertex.getType().equals("InputField") && + vertex.get("name").equals(inputField.getName())).get(); + handleInputField(inputFieldVertex, inputField, schemaGraph, graphQLSchema); + } + } + + private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField inputField, SchemaGraph + schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInputType type = inputField.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(inputFieldVertex, typeVertex); + typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + } + + private void handleUnion(Vertex unionVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLUnionType unionType = (GraphQLUnionType) graphQLSchema.getType(unionVertex.get("name")); + List types = unionType.getTypes(); + for (GraphQLNamedOutputType unionMemberType : types) { + Vertex unionMemberVertex = assertNotNull(schemaGraph.getType(unionMemberType.getName())); + schemaGraph.addEdge(new Edge(unionVertex, unionMemberVertex)); + } + } + + private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInterfaceType interfaceType = (GraphQLInterfaceType) graphQLSchema.getType(interfaceVertex.get("name")); + + for (GraphQLNamedOutputType implementsInterface : interfaceType.getInterfaces()) { + Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName())); + schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex)); + } + + List fieldDefinitions = interfaceType.getFieldDefinitions(); + for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { + Vertex fieldVertex = schemaGraph.findTargetVertex(interfaceVertex, vertex -> vertex.getType().equals("Field") && + vertex.get("name").equals(fieldDefinition.getName())).get(); + handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); + } + + } + + private void handleObjectVertex(Vertex objectVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLObjectType objectType = graphQLSchema.getObjectType(objectVertex.get("name")); + + for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { + Vertex interfaceVertex = assertNotNull(schemaGraph.getType(interfaceType.getName())); + schemaGraph.addEdge(new Edge(objectVertex, interfaceVertex)); + } + + List fieldDefinitions = objectType.getFieldDefinitions(); + for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { + Vertex fieldVertex = schemaGraph.findTargetVertex(objectVertex, vertex -> vertex.getType().equals("Field") && + vertex.get("name").equals(fieldDefinition.getName())).get(); + handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); + } + } + + private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinition, SchemaGraph + schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLOutputType type = fieldDefinition.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(fieldVertex, typeVertex); + typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + + for (GraphQLArgument graphQLArgument : fieldDefinition.getArguments()) { + Vertex argumentVertex = schemaGraph.findTargetVertex(fieldVertex, vertex -> vertex.getType().equals("Argument") && + vertex.get("name").equals(graphQLArgument.getName())).get(); + handleArgument(argumentVertex, graphQLArgument, schemaGraph); + } + } + + private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { + GraphQLInputType type = graphQLArgument.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(argumentVertex, typeVertex); + typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + } + + private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph) { + Vertex objectVertex = new Vertex("Object"); + objectVertex.add("name", graphQLObjectType.getName()); + objectVertex.add("description", graphQLObjectType.getDescription()); + for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + schemaGraph.addVertex(newFieldVertex); + schemaGraph.addEdge(new Edge(objectVertex, newFieldVertex)); + } + schemaGraph.addVertex(objectVertex); + schemaGraph.addType(graphQLObjectType.getName(), objectVertex); + cratedAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); + } + + private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph) { + Vertex fieldVertex = new Vertex("Field"); + fieldVertex.add("name", graphQLFieldDefinition.getName()); + fieldVertex.add("description", graphQLFieldDefinition.getDescription()); + for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { + Vertex argumentVertex = newArgument(argument, schemaGraph); + schemaGraph.addVertex(argumentVertex); + schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex)); + } + cratedAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph); + return fieldVertex; + } + + private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { + Vertex vertex = new Vertex("Argument"); + vertex.add("name", graphQLArgument.getName()); + vertex.add("description", graphQLArgument.getDescription()); + cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); + return vertex; + } + + private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { + Vertex scalarVertex = new Vertex("Scalar"); + scalarVertex.add("name", scalarType.getName()); + scalarVertex.add("description", scalarType.getDescription()); + schemaGraph.addVertex(scalarVertex); + schemaGraph.addType(scalarType.getName(), scalarVertex); + cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); + } + + private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph) { + Vertex interfaceVertex = new Vertex("Interface"); + interfaceVertex.add("name", interfaceType.getName()); + interfaceVertex.add("description", interfaceType.getDescription()); + for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + schemaGraph.addVertex(newFieldVertex); + schemaGraph.addEdge(new Edge(interfaceVertex, newFieldVertex)); + } + schemaGraph.addVertex(interfaceVertex); + schemaGraph.addType(interfaceType.getName(), interfaceVertex); + cratedAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); + } + + private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { + Vertex enumVertex = new Vertex("Enum"); + enumVertex.add("name", enumType.getName()); + enumVertex.add("description", enumType.getDescription()); + for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { + Vertex enumValueVertex = new Vertex("EnumValue"); + enumValueVertex.add("name", enumValue.getName()); + schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); + cratedAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph); + } + schemaGraph.addVertex(enumVertex); + schemaGraph.addType(enumType.getName(), enumVertex); + cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); + } + + private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { + Vertex unionVertex = new Vertex("Union"); + unionVertex.add("name", unionType.getName()); + unionVertex.add("description", unionType.getDescription()); + schemaGraph.addVertex(unionVertex); + schemaGraph.addType(unionType.getName(), unionVertex); + cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); + } + + private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph) { + Vertex inputObjectVertex = new Vertex("InputObject"); + inputObjectVertex.add("name", inputObject.getName()); + inputObjectVertex.add("description", inputObject.getDescription()); + for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { + Vertex newInputField = newInputField(inputObjectField, schemaGraph); + Edge newEdge = new Edge(inputObjectVertex, newInputField); + schemaGraph.addEdge(newEdge); + cratedAppliedDirectives(inputObjectVertex, inputObjectField.getDirectives(), schemaGraph); + } + schemaGraph.addVertex(inputObjectVertex); + schemaGraph.addType(inputObject.getName(), inputObjectVertex); + cratedAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph); + } + + private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph + schemaGraph) { + for (GraphQLDirective appliedDirective : appliedDirectives) { + Vertex appliedDirectiveVertex = new Vertex("AppliedDirective"); + appliedDirectiveVertex.add("name", appliedDirective.getName()); + for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { + Vertex appliedArgumentVertex = new Vertex("AppliedArgument"); + appliedArgumentVertex.add("name", appliedArgument.getName()); + appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); + schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); + } + schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); + } + } + + private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { + Vertex directiveVertex = new Vertex("Directive"); + directiveVertex.add("name", directive.getName()); + directiveVertex.add("description", directive.getDescription()); + for (GraphQLArgument argument : directive.getArguments()) { + Vertex argumentVertex = newArgument(argument, schemaGraph); + schemaGraph.addVertex(argumentVertex); + schemaGraph.addEdge(new Edge(directiveVertex, argumentVertex)); + } + schemaGraph.addDirective(directive.getName(),directiveVertex); + schemaGraph.addVertex(directiveVertex); + } + + private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph) { + Vertex vertex = new Vertex("InputField"); + vertex.add("name", inputField.getName()); + vertex.add("description", inputField.getDescription()); + cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); + return vertex; + } +} diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java new file mode 100644 index 0000000000..3989097018 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -0,0 +1,62 @@ +package graphql.schema.diffing; + + +import graphql.collect.ImmutableKit; + +import java.util.*; +import java.util.function.Predicate; + +public class SchemaGraph { + + private List vertices = new ArrayList<>(); + private Map> fromEdges = new LinkedHashMap<>(); + private List edges = new ArrayList<>(); + + private Map typesByName = new LinkedHashMap<>(); + private Map directivesByName = new LinkedHashMap<>(); + + public void addVertex(Vertex vertex) { + vertices.add(vertex); + } + + public void addEdge(Edge edge) { + edges.add(edge); + fromEdges.computeIfAbsent(edge.getFrom(), ignored -> new ArrayList<>()).add(edge); + } + + public List getEdges(Vertex from) { + return fromEdges.get(from); + } + + public List getTargetVertices(Vertex from) { + return ImmutableKit.map(fromEdges.get(from), Edge::getTo); + } + + public List getVertices() { + return vertices; + } + + public void addType(String name, Vertex vertex) { + typesByName.put(name, vertex); + } + + public void addDirective(String name, Vertex vertex) { + directivesByName.put(name, vertex); + } + + public Vertex getType(String name) { + return typesByName.get(name); + } + + public Vertex getDirective(String name) { + return directivesByName.get(name); + } + + public Optional findTargetVertex(Vertex from, Predicate vertexPredicate) { + return fromEdges.get(from).stream().map(Edge::getTo).filter(vertexPredicate).findFirst(); + } + + public int size() { + return vertices.size(); + } +} diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java new file mode 100644 index 0000000000..28b3a2d5f2 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -0,0 +1,25 @@ +package graphql.schema.diffing; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class Vertex { + + private String type; + private Map properties = new LinkedHashMap<>(); + + public Vertex(String type) { + this.type = type; + } + + public void add(String propName, Object propValue) { + properties.put(propName,propValue); + } + + public String getType() { + return type; + } + public T get(String propName) { + return (T) properties.get(propName); + } +} diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy new file mode 100644 index 0000000000..b65bb81591 --- /dev/null +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -0,0 +1,25 @@ +package graphql.schema.diffing + +import spock.lang.Specification + +import static graphql.TestUtil.schema + +class SchemaDiffingTest extends Specification { + + + def "test"() { + given: + def schema = schema(""" + type Query { + hello: String + } + """) + + when: + def schemaGraph = new SchemaDiffing().createGraph(schema) + + then: + schemaGraph.size() == 64 + + } +} From f4719236658aa4dcfe225f1fe3db93703d7b1821 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 14 Nov 2021 16:48:52 +1100 Subject: [PATCH 002/294] maybe something working --- .../java/graphql/schema/diffing/Edge.java | 15 +- .../schema/diffing/HungarianAlgorithm.java | 334 +++++++++++++ .../graphql/schema/diffing/SchemaDiffing.java | 456 ++++++++---------- .../graphql/schema/diffing/SchemaGraph.java | 15 +- .../schema/diffing/SchemaGraphFactory.java | 311 ++++++++++++ .../java/graphql/schema/diffing/Vertex.java | 4 + .../schema/diffing/SchemaDiffingTest.groovy | 21 + 7 files changed, 887 insertions(+), 269 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/HungarianAlgorithm.java create mode 100644 src/main/java/graphql/schema/diffing/SchemaGraphFactory.java diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index c6987fc36e..e25261139e 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -1,13 +1,10 @@ package graphql.schema.diffing; -import java.util.LinkedHashMap; -import java.util.Map; - public class Edge { private Vertex from; private Vertex to; - private Map properties = new LinkedHashMap<>(); + private String label = ""; public Edge(Vertex from, Vertex to) { this.from = from; @@ -31,13 +28,11 @@ public void setTo(Vertex to) { } - public void add(String propName, Object propValue) { - properties.put(propName, propValue); + public void setLabel(String label) { + this.label = label; } - public T get(String propName) { - return (T) properties.get(propName); + public String getLabel() { + return label; } - - } diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java new file mode 100644 index 0000000000..d86307ca2a --- /dev/null +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -0,0 +1,334 @@ +package graphql.schema.diffing; + +import java.util.Arrays; + +/* Copyright (c) 2012 Kevin L. Stern + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * An implementation of the Hungarian algorithm for solving the assignment + * problem. An instance of the assignment problem consists of a number of + * workers along with a number of jobs and a cost matrix which gives the cost of + * assigning the i'th worker to the j'th job at position (i, j). The goal is to + * find an assignment of workers to jobs so that no job is assigned more than + * one worker and so that no worker is assigned to more than one job in such a + * manner so as to minimize the total cost of completing the jobs. + *

+ *

+ * An assignment for a cost matrix that has more workers than jobs will + * necessarily include unassigned workers, indicated by an assignment value of + * -1; in no other circumstance will there be unassigned workers. Similarly, an + * assignment for a cost matrix that has more jobs than workers will necessarily + * include unassigned jobs; in no other circumstance will there be unassigned + * jobs. For completeness, an assignment for a square cost matrix will give + * exactly one unique worker to each job. + *

+ *

+ * This version of the Hungarian algorithm runs in time O(n^3), where n is the + * maximum among the number of workers and the number of jobs. + * + * @author Kevin L. Stern + */ +public class HungarianAlgorithm { + private final double[][] costMatrix; + private final int rows, cols, dim; + private final double[] labelByWorker, labelByJob; + private final int[] minSlackWorkerByJob; + private final double[] minSlackValueByJob; + private final int[] matchJobByWorker, matchWorkerByJob; + private final int[] parentWorkerByCommittedJob; + private final boolean[] committedWorkers; + + /** + * Construct an instance of the algorithm. + * + * @param costMatrix the cost matrix, where matrix[i][j] holds the cost of assigning + * worker i to job j, for all i, j. The cost matrix must not be + * irregular in the sense that all rows must be the same length; in + * addition, all entries must be non-infinite numbers. + */ + public HungarianAlgorithm(double[][] costMatrix) { + this.dim = Math.max(costMatrix.length, costMatrix[0].length); + this.rows = costMatrix.length; + this.cols = costMatrix[0].length; + this.costMatrix = new double[this.dim][this.dim]; + for (int w = 0; w < this.dim; w++) { + if (w < costMatrix.length) { + if (costMatrix[w].length != this.cols) { + throw new IllegalArgumentException("Irregular cost matrix"); + } + for (int j = 0; j < this.cols; j++) { + if (Double.isInfinite(costMatrix[w][j])) { + throw new IllegalArgumentException("Infinite cost"); + } + if (Double.isNaN(costMatrix[w][j])) { + throw new IllegalArgumentException("NaN cost"); + } + } + this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim); + } else { + this.costMatrix[w] = new double[this.dim]; + } + } + labelByWorker = new double[this.dim]; + labelByJob = new double[this.dim]; + minSlackWorkerByJob = new int[this.dim]; + minSlackValueByJob = new double[this.dim]; + committedWorkers = new boolean[this.dim]; + parentWorkerByCommittedJob = new int[this.dim]; + matchJobByWorker = new int[this.dim]; + Arrays.fill(matchJobByWorker, -1); + matchWorkerByJob = new int[this.dim]; + Arrays.fill(matchWorkerByJob, -1); + } + + /** + * Compute an initial feasible solution by assigning zero labels to the + * workers and by assigning to each job a label equal to the minimum cost + * among its incident edges. + */ + protected void computeInitialFeasibleSolution() { + for (int j = 0; j < dim; j++) { + labelByJob[j] = Double.POSITIVE_INFINITY; + } + for (int w = 0; w < dim; w++) { + for (int j = 0; j < dim; j++) { + if (costMatrix[w][j] < labelByJob[j]) { + labelByJob[j] = costMatrix[w][j]; + } + } + } + } + + /** + * Execute the algorithm. + * + * @return the minimum cost matching of workers to jobs based upon the + * provided cost matrix. A matching value of -1 indicates that the + * corresponding worker is unassigned. + */ + public int[] execute() { + /* + * Heuristics to improve performance: Reduce rows and columns by their + * smallest element, compute an initial non-zero dual feasible solution and + * create a greedy matching from workers to jobs of the cost matrix. + */ + reduce(); + computeInitialFeasibleSolution(); + greedyMatch(); + + int w = fetchUnmatchedWorker(); + while (w < dim) { + initializePhase(w); + executePhase(); + w = fetchUnmatchedWorker(); + } + int[] result = Arrays.copyOf(matchJobByWorker, rows); + for (w = 0; w < result.length; w++) { + if (result[w] >= cols) { + result[w] = -1; + } + } + return result; + } + + /** + * Execute a single phase of the algorithm. A phase of the Hungarian algorithm + * consists of building a set of committed workers and a set of committed jobs + * from a root unmatched worker by following alternating unmatched/matched + * zero-slack edges. If an unmatched job is encountered, then an augmenting + * path has been found and the matching is grown. If the connected zero-slack + * edges have been exhausted, the labels of committed workers are increased by + * the minimum slack among committed workers and non-committed jobs to create + * more zero-slack edges (the labels of committed jobs are simultaneously + * decreased by the same amount in order to maintain a feasible labeling). + *

+ *

+ * The runtime of a single phase of the algorithm is O(n^2), where n is the + * dimension of the internal square cost matrix, since each edge is visited at + * most once and since increasing the labeling is accomplished in time O(n) by + * maintaining the minimum slack values among non-committed jobs. When a phase + * completes, the matching will have increased in size. + */ + protected void executePhase() { + while (true) { + int minSlackWorker = -1, minSlackJob = -1; + double minSlackValue = Double.POSITIVE_INFINITY; + for (int j = 0; j < dim; j++) { + if (parentWorkerByCommittedJob[j] == -1) { + if (minSlackValueByJob[j] < minSlackValue) { + minSlackValue = minSlackValueByJob[j]; + minSlackWorker = minSlackWorkerByJob[j]; + minSlackJob = j; + } + } + } + if (minSlackValue > 0) { + updateLabeling(minSlackValue); + } + parentWorkerByCommittedJob[minSlackJob] = minSlackWorker; + if (matchWorkerByJob[minSlackJob] == -1) { + /* + * An augmenting path has been found. + */ + int committedJob = minSlackJob; + int parentWorker = parentWorkerByCommittedJob[committedJob]; + while (true) { + int temp = matchJobByWorker[parentWorker]; + match(parentWorker, committedJob); + committedJob = temp; + if (committedJob == -1) { + break; + } + parentWorker = parentWorkerByCommittedJob[committedJob]; + } + return; + } else { + /* + * Update slack values since we increased the size of the committed + * workers set. + */ + int worker = matchWorkerByJob[minSlackJob]; + committedWorkers[worker] = true; + for (int j = 0; j < dim; j++) { + if (parentWorkerByCommittedJob[j] == -1) { + double slack = costMatrix[worker][j] - labelByWorker[worker] + - labelByJob[j]; + if (minSlackValueByJob[j] > slack) { + minSlackValueByJob[j] = slack; + minSlackWorkerByJob[j] = worker; + } + } + } + } + } + } + + /** + * @return the first unmatched worker or {@link #dim} if none. + */ + protected int fetchUnmatchedWorker() { + int w; + for (w = 0; w < dim; w++) { + if (matchJobByWorker[w] == -1) { + break; + } + } + return w; + } + + /** + * Find a valid matching by greedily selecting among zero-cost matchings. This + * is a heuristic to jump-start the augmentation algorithm. + */ + protected void greedyMatch() { + for (int w = 0; w < dim; w++) { + for (int j = 0; j < dim; j++) { + if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1 + && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) { + match(w, j); + } + } + } + } + + /** + * Initialize the next phase of the algorithm by clearing the committed + * workers and jobs sets and by initializing the slack arrays to the values + * corresponding to the specified root worker. + * + * @param w the worker at which to root the next phase. + */ + protected void initializePhase(int w) { + Arrays.fill(committedWorkers, false); + Arrays.fill(parentWorkerByCommittedJob, -1); + committedWorkers[w] = true; + for (int j = 0; j < dim; j++) { + minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] + - labelByJob[j]; + minSlackWorkerByJob[j] = w; + } + } + + /** + * Helper method to record a matching between worker w and job j. + */ + protected void match(int w, int j) { + matchJobByWorker[w] = j; + matchWorkerByJob[j] = w; + } + + /** + * Reduce the cost matrix by subtracting the smallest element of each row from + * all elements of the row as well as the smallest element of each column from + * all elements of the column. Note that an optimal assignment for a reduced + * cost matrix is optimal for the original cost matrix. + */ + protected void reduce() { + for (int w = 0; w < dim; w++) { + double min = Double.POSITIVE_INFINITY; + for (int j = 0; j < dim; j++) { + if (costMatrix[w][j] < min) { + min = costMatrix[w][j]; + } + } + for (int j = 0; j < dim; j++) { + costMatrix[w][j] -= min; + } + } + double[] min = new double[dim]; + for (int j = 0; j < dim; j++) { + min[j] = Double.POSITIVE_INFINITY; + } + for (int w = 0; w < dim; w++) { + for (int j = 0; j < dim; j++) { + if (costMatrix[w][j] < min[j]) { + min[j] = costMatrix[w][j]; + } + } + } + for (int w = 0; w < dim; w++) { + for (int j = 0; j < dim; j++) { + costMatrix[w][j] -= min[j]; + } + } + } + + /** + * Update labels with the specified slack by adding the slack value for + * committed workers and by subtracting the slack value for committed jobs. In + * addition, update the minimum slack values appropriately. + */ + protected void updateLabeling(double slack) { + for (int w = 0; w < dim; w++) { + if (committedWorkers[w]) { + labelByWorker[w] += slack; + } + } + for (int j = 0; j < dim; j++) { + if (parentWorkerByCommittedJob[j] != -1) { + labelByJob[j] -= slack; + } else { + minSlackValueByJob[j] -= slack; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index b4df204ce5..1cffbfe020 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,309 +1,249 @@ package graphql.schema.diffing; +import com.google.common.collect.*; +import com.google.common.util.concurrent.AtomicDouble; +import graphql.Assert; +import graphql.collect.ImmutableKit; import graphql.schema.*; import graphql.util.*; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import static graphql.Assert.assertNotNull; +import static graphql.Assert.assertTrue; public class SchemaDiffing { - public static void diff(GraphQLSchema from, GraphQLSchema to) { + private static class Mapping { } - public SchemaGraph createGraph(GraphQLSchema schema) { - Set roots = new LinkedHashSet<>(); - roots.add(schema.getQueryType()); - if (schema.isSupportingMutations()) { - roots.add(schema.getMutationType()); + private static class MappingEntry { + public MappingEntry(ImmutableList partialMapping, int level, double lowerBoundCost, ImmutableList candidates) { + this.partialMapping = partialMapping; + this.level = level; + this.lowerBoundCost = lowerBoundCost; + this.candidates = candidates; } - if (schema.isSupportingSubscriptions()) { - roots.add(schema.getSubscriptionType()); + + public MappingEntry() { + } - roots.addAll(schema.getAdditionalTypes()); - roots.addAll(schema.getDirectives()); - roots.addAll(schema.getSchemaDirectives()); - roots.add(schema.getIntrospectionSchemaType()); - Traverser traverser = Traverser.depthFirst(GraphQLSchemaElement::getChildren); - SchemaGraph schemaGraph = new SchemaGraph(); - traverser.traverse(roots, new TraverserVisitor() { - @Override - public TraversalControl enter(TraverserContext context) { - if (context.thisNode() instanceof GraphQLObjectType) { - newObject((GraphQLObjectType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLInterfaceType) { - newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLUnionType) { - newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLScalarType) { - newScalar((GraphQLScalarType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLInputObjectType) { - newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLEnumType) { - newEnum((GraphQLEnumType) context.thisNode(), schemaGraph); - } - if (context.thisNode() instanceof GraphQLDirective) { - newDirective((GraphQLDirective) context.thisNode(), schemaGraph); - } - return TraversalControl.CONTINUE; - } - @Override - public TraversalControl leave(TraverserContext context) { - return TraversalControl.CONTINUE; - } - }); + // target vertices which the fist `level` vertices of source graph are mapped to + ImmutableList partialMapping = ImmutableList.builder().build(); + int level; + double lowerBoundCost; + ImmutableList candidates = ImmutableList.builder().build(); + } + + public void diff(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + SchemaGraph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); + SchemaGraph targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); + // we assert here that the graphs have the same size. The algorithm depends on it + assertTrue(sourceGraph.size() == targetGraph.size()); + int graphSize = sourceGraph.size(); - for (Vertex vertex : schemaGraph.getVertices()) { - if ("Object".equals(vertex.getType())) { - handleObjectVertex(vertex, schemaGraph, schema); + AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); + AtomicReference> result = new AtomicReference<>(); + PriorityQueue queue = new PriorityQueue((mappingEntry1, mappingEntry2) -> { + int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); + if (compareResult == 0) { + return (-1) * Integer.compare(mappingEntry1.level, mappingEntry2.level); + } else { + return compareResult; } - if ("Interface".equals(vertex.getType())) { - handleInterfaceVertex(vertex, schemaGraph, schema); + }); + queue.add(new MappingEntry()); + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + continue; } - if ("Union".equals(vertex.getType())) { - handleUnion(vertex, schemaGraph, schema); + // generate sibling + if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { + // we need to remove the last mapping + ImmutableList parentMapping = mappingEntry.partialMapping.subList(0, mappingEntry.partialMapping.size() - 1); + genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, result, sourceGraph, targetGraph); } - if ("InputObject".equals(vertex.getType())) { - handleInputObject(vertex, schemaGraph, schema); + // generate children + if (mappingEntry.level < graphSize) { + // candidates are the vertices in target, of which are not used yet in partialMapping + List childCandidates = new ArrayList<>(targetGraph.getVertices()); + childCandidates.removeAll(mappingEntry.partialMapping); + genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, ImmutableList.copyOf(childCandidates), queue, upperBoundCost, result, sourceGraph, targetGraph); } - if ("AppliedDirective".equals(vertex.getType())) { - handleAppliedDirective(vertex, schemaGraph, schema); + } + System.out.println("ged cost: " + upperBoundCost.doubleValue()); + System.out.println("mapping : " + result); + } + + // level starts at 1 indicating the level in the search tree to look for the next mapping + private void genNextMapping(ImmutableList partialMappingTargetList, + int level, + ImmutableList candidates, + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference> bestMapping, + SchemaGraph sourceGraph, + SchemaGraph targetGraph) { + List sourceList = sourceGraph.getVertices(); + List targetList = targetGraph.getVertices(); + ArrayList availableTargetVertices = new ArrayList<>(targetList); + availableTargetVertices.removeAll(partialMappingTargetList); + Vertex v_i = sourceList.get(level); + int costMatrixSize = sourceList.size() - level + 1; + double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + + List partialMappingSourceList = new ArrayList<>(partialMappingTargetList.subList(0, level - 1)); + + // we are skipping the first level -i indeces + for (int i = level - 1; i < sourceList.size(); i++) { + Vertex v = sourceList.get(i); + int j = 0; + for (Vertex u : availableTargetVertices) { + if (v == v_i && !candidates.contains(u)) { + costMatrix[i - level + 1][j] = Integer.MAX_VALUE; + } else { + costMatrix[i - level + 1][j] = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMappingSourceList, partialMappingTargetList); + } + j++; } } - return schemaGraph; - } - private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); - schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); - } + // find out the best extension + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); + int[] assignments = hungarianAlgorithm.execute(); + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = targetList.get(v_i_target_Index); - private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) graphQLSchema.getType(inputObject.get("name")); - List inputFields = inputObjectType.getFields(); - for (GraphQLInputObjectField inputField : inputFields) { - Vertex inputFieldVertex = schemaGraph.findTargetVertex(inputObject, vertex -> vertex.getType().equals("InputField") && - vertex.get("name").equals(inputField.getName())).get(); - handleInputField(inputFieldVertex, inputField, schemaGraph, graphQLSchema); - } - } + double bestExtensionLowerBound = costMatrix[0][v_i_target_Index]; + if (bestExtensionLowerBound < upperBound.doubleValue()) { + ImmutableList newMapping = ImmutableKit.addToList(partialMappingTargetList, bestExtensionTargetVertex); + ImmutableList newCandidates = removeVertex(candidates, bestExtensionTargetVertex); + queue.add(new MappingEntry(newMapping, level, bestExtensionLowerBound, newCandidates)); - private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField inputField, SchemaGraph - schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLInputType type = inputField.getType(); - GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(inputFieldVertex, typeVertex); - typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); - schemaGraph.addEdge(typeEdge); - } + // we have a full mapping from the cost matrix + List fullMapping = new ArrayList<>(partialMappingTargetList); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(availableTargetVertices.get(assignments[i])); + } + // the cost of the full mapping is the new upperBound (aka the current best mapping) + upperBound.set(editorialCostForFullMapping(fullMapping, sourceGraph, targetGraph)); + bestMapping.set(fullMapping); + } + } + + // minimum number of edit operations for a full mapping + private int editorialCostForFullMapping(List fullMapping, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + int cost = 0; + int i = 0; + for (Vertex v : sourceGraph.getVertices()) { + Vertex targetVertex = fullMapping.get(i++); + // Vertex changing (relabeling) + boolean equalNodes = v.getType().equals(targetVertex.getType()) && v.getProperties().equals(targetVertex.getProperties()); + if (!equalNodes) { + cost++; + } + List edges = sourceGraph.getEdges(v); + // edge deletion or relabeling + for (Edge sourceEdge : edges) { + Vertex targetFrom = getTargetVertex(fullMapping, sourceEdge.getFrom(), sourceGraph); + Vertex targetTo = getTargetVertex(fullMapping, sourceEdge.getTo(), sourceGraph); + Edge targetEdge = targetGraph.getEdge(targetFrom, targetTo); + if (targetEdge == null || !sourceEdge.getLabel().equals(targetEdge.getLabel())) { + cost++; + } + } - private void handleUnion(Vertex unionVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLUnionType unionType = (GraphQLUnionType) graphQLSchema.getType(unionVertex.get("name")); - List types = unionType.getTypes(); - for (GraphQLNamedOutputType unionMemberType : types) { - Vertex unionMemberVertex = assertNotNull(schemaGraph.getType(unionMemberType.getName())); - schemaGraph.addEdge(new Edge(unionVertex, unionMemberVertex)); + // edge insertion + for (Edge targetEdge : targetGraph.getEdges()) { + Vertex sourceFrom = getSourceVertex(fullMapping, targetEdge.getFrom(), sourceGraph); + Vertex sourceTo = getSourceVertex(fullMapping, targetEdge.getTo(), sourceGraph); + if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { + cost++; + } + } } + return cost; } - private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLInterfaceType interfaceType = (GraphQLInterfaceType) graphQLSchema.getType(interfaceVertex.get("name")); - - for (GraphQLNamedOutputType implementsInterface : interfaceType.getInterfaces()) { - Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName())); - schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex)); + // TODO: very inefficient + private Vertex getTargetVertex(List mappingTargetVertices, Vertex sourceVertex, SchemaGraph sourceGraph) { + for (int i = 0; i < sourceGraph.getVertices().size(); i++) { + Vertex v = sourceGraph.getVertices().get(i); + if (v != sourceVertex) continue; + return mappingTargetVertices.get(i); } - - List fieldDefinitions = interfaceType.getFieldDefinitions(); - for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { - Vertex fieldVertex = schemaGraph.findTargetVertex(interfaceVertex, vertex -> vertex.getType().equals("Field") && - vertex.get("name").equals(fieldDefinition.getName())).get(); - handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); - } - + return Assert.assertShouldNeverHappen(); } - private void handleObjectVertex(Vertex objectVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLObjectType objectType = graphQLSchema.getObjectType(objectVertex.get("name")); - - for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { - Vertex interfaceVertex = assertNotNull(schemaGraph.getType(interfaceType.getName())); - schemaGraph.addEdge(new Edge(objectVertex, interfaceVertex)); - } - - List fieldDefinitions = objectType.getFieldDefinitions(); - for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { - Vertex fieldVertex = schemaGraph.findTargetVertex(objectVertex, vertex -> vertex.getType().equals("Field") && - vertex.get("name").equals(fieldDefinition.getName())).get(); - handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); - } + private Vertex getSourceVertex(List mappingTargetVertices, Vertex targetVertex, SchemaGraph sourceGraph) { + int index = mappingTargetVertices.indexOf(targetVertex); + assertTrue(index > -1); + return Assert.assertNotNull(sourceGraph.getVertices().get(index)); } - private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinition, SchemaGraph - schemaGraph, GraphQLSchema graphQLSchema) { - GraphQLOutputType type = fieldDefinition.getType(); - GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(fieldVertex, typeVertex); - typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); - schemaGraph.addEdge(typeEdge); + // lower bound mapping cost between for v -> u in respect to a partial mapping + private double calcLowerBoundMappingCost(Vertex v, + Vertex u, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + List partialMappingSourceList, + List partialMappingTargetList) { + boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); + // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges + // which are adjacent of u (resp. v) which are inner edges - for (GraphQLArgument graphQLArgument : fieldDefinition.getArguments()) { - Vertex argumentVertex = schemaGraph.findTargetVertex(fieldVertex, vertex -> vertex.getType().equals("Argument") && - vertex.get("name").equals(graphQLArgument.getName())).get(); - handleArgument(argumentVertex, graphQLArgument, schemaGraph); - } - } - - private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { - GraphQLInputType type = graphQLArgument.getType(); - GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(argumentVertex, typeVertex); - typeEdge.add("type", GraphQLTypeUtil.simplePrint(type)); - schemaGraph.addEdge(typeEdge); - } + List adjacentEdgesV = sourceGraph.getEdges(v); + Set nonMappedSourceVertices = nonMappedVertices(sourceGraph.getVertices(), partialMappingSourceList); + Multiset multisetLabelsV = HashMultiset.create(); - private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph) { - Vertex objectVertex = new Vertex("Object"); - objectVertex.add("name", graphQLObjectType.getName()); - objectVertex.add("description", graphQLObjectType.getDescription()); - for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { - Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); - schemaGraph.addVertex(newFieldVertex); - schemaGraph.addEdge(new Edge(objectVertex, newFieldVertex)); + for (Edge edge : adjacentEdgesV) { + if (nonMappedSourceVertices.contains(edge.getFrom()) && nonMappedSourceVertices.contains(edge.getTo())) { + multisetLabelsV.add(edge.getLabel()); + } } - schemaGraph.addVertex(objectVertex); - schemaGraph.addType(graphQLObjectType.getName(), objectVertex); - cratedAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); - } - - private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph) { - Vertex fieldVertex = new Vertex("Field"); - fieldVertex.add("name", graphQLFieldDefinition.getName()); - fieldVertex.add("description", graphQLFieldDefinition.getDescription()); - for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { - Vertex argumentVertex = newArgument(argument, schemaGraph); - schemaGraph.addVertex(argumentVertex); - schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex)); + List adjacentEdgesU = targetGraph.getEdges(u); + Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); + Multiset multisetLabelsU = HashMultiset.create(); + for (Edge edge : adjacentEdgesU) { + if (nonMappedTargetVertices.contains(edge.getFrom()) && nonMappedTargetVertices.contains(edge.getTo())) { + multisetLabelsU.add(edge.getLabel()); + } } - cratedAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph); - return fieldVertex; - } - - private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { - Vertex vertex = new Vertex("Argument"); - vertex.add("name", graphQLArgument.getName()); - vertex.add("description", graphQLArgument.getDescription()); - cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); - return vertex; - } - private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { - Vertex scalarVertex = new Vertex("Scalar"); - scalarVertex.add("name", scalarType.getName()); - scalarVertex.add("description", scalarType.getDescription()); - schemaGraph.addVertex(scalarVertex); - schemaGraph.addType(scalarType.getName(), scalarVertex); - cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); - } - - private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph) { - Vertex interfaceVertex = new Vertex("Interface"); - interfaceVertex.add("name", interfaceType.getName()); - interfaceVertex.add("description", interfaceType.getDescription()); - for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { - Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); - schemaGraph.addVertex(newFieldVertex); - schemaGraph.addEdge(new Edge(interfaceVertex, newFieldVertex)); + int anchoredVerticesCost = 0; + for (int i = 0; i < partialMappingSourceList.size(); i++) { + Vertex vPrime = partialMappingSourceList.get(i); + Vertex mappedVPrime = partialMappingTargetList.get(i); + Edge sourceEdge = sourceGraph.getEdge(v, vPrime); + Edge targetEdge = targetGraph.getEdge(u, mappedVPrime); + if (sourceEdge != targetEdge) { + anchoredVerticesCost++; + } } - schemaGraph.addVertex(interfaceVertex); - schemaGraph.addType(interfaceType.getName(), interfaceVertex); - cratedAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); - } + Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); + int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { - Vertex enumVertex = new Vertex("Enum"); - enumVertex.add("name", enumType.getName()); - enumVertex.add("description", enumType.getDescription()); - for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { - Vertex enumValueVertex = new Vertex("EnumValue"); - enumValueVertex.add("name", enumValue.getName()); - schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); - cratedAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph); - } - schemaGraph.addVertex(enumVertex); - schemaGraph.addType(enumType.getName(), enumVertex); - cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); + return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } - private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { - Vertex unionVertex = new Vertex("Union"); - unionVertex.add("name", unionType.getName()); - unionVertex.add("description", unionType.getDescription()); - schemaGraph.addVertex(unionVertex); - schemaGraph.addType(unionType.getName(), unionVertex); - cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); + private Set nonMappedVertices(List allVertices, List partialMapping) { + Set set = new LinkedHashSet<>(allVertices); + partialMapping.forEach(set::remove); + return set; } - private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph) { - Vertex inputObjectVertex = new Vertex("InputObject"); - inputObjectVertex.add("name", inputObject.getName()); - inputObjectVertex.add("description", inputObject.getDescription()); - for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { - Vertex newInputField = newInputField(inputObjectField, schemaGraph); - Edge newEdge = new Edge(inputObjectVertex, newInputField); - schemaGraph.addEdge(newEdge); - cratedAppliedDirectives(inputObjectVertex, inputObjectField.getDirectives(), schemaGraph); - } - schemaGraph.addVertex(inputObjectVertex); - schemaGraph.addType(inputObject.getName(), inputObjectVertex); - cratedAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph); - } - private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph - schemaGraph) { - for (GraphQLDirective appliedDirective : appliedDirectives) { - Vertex appliedDirectiveVertex = new Vertex("AppliedDirective"); - appliedDirectiveVertex.add("name", appliedDirective.getName()); - for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { - Vertex appliedArgumentVertex = new Vertex("AppliedArgument"); - appliedArgumentVertex.add("name", appliedArgument.getName()); - appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); - schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); + private ImmutableList removeVertex(ImmutableList list, Vertex toRemove) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (Vertex vertex : list) { + if (vertex != toRemove) { + builder.add(vertex); } - schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); } + return builder.build(); } - private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { - Vertex directiveVertex = new Vertex("Directive"); - directiveVertex.add("name", directive.getName()); - directiveVertex.add("description", directive.getDescription()); - for (GraphQLArgument argument : directive.getArguments()) { - Vertex argumentVertex = newArgument(argument, schemaGraph); - schemaGraph.addVertex(argumentVertex); - schemaGraph.addEdge(new Edge(directiveVertex, argumentVertex)); - } - schemaGraph.addDirective(directive.getName(),directiveVertex); - schemaGraph.addVertex(directiveVertex); - } - - private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph) { - Vertex vertex = new Vertex("InputField"); - vertex.add("name", inputField.getName()); - vertex.add("description", inputField.getDescription()); - cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); - return vertex; - } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 3989097018..1f2605c7f5 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -1,7 +1,10 @@ package graphql.schema.diffing; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; import graphql.collect.ImmutableKit; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Predicate; @@ -14,6 +17,7 @@ public class SchemaGraph { private Map typesByName = new LinkedHashMap<>(); private Map directivesByName = new LinkedHashMap<>(); + private Table edgeByVertexPair = HashBasedTable.create(); public void addVertex(Vertex vertex) { vertices.add(vertex); @@ -22,10 +26,19 @@ public void addVertex(Vertex vertex) { public void addEdge(Edge edge) { edges.add(edge); fromEdges.computeIfAbsent(edge.getFrom(), ignored -> new ArrayList<>()).add(edge); + edgeByVertexPair.put(edge.getFrom(), edge.getTo(), edge); } public List getEdges(Vertex from) { - return fromEdges.get(from); + return fromEdges.getOrDefault(from, Collections.emptyList()); + } + + public List getEdges() { + return edges; + } + + public @Nullable Edge getEdge(Vertex from, Vertex to) { + return edgeByVertexPair.get(from, to); } public List getTargetVertices(Vertex from) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java new file mode 100644 index 0000000000..c1f627dc8e --- /dev/null +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -0,0 +1,311 @@ +package graphql.schema.diffing; + +import graphql.schema.*; +import graphql.util.TraversalControl; +import graphql.util.Traverser; +import graphql.util.TraverserContext; +import graphql.util.TraverserVisitor; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static graphql.Assert.assertNotNull; + +public class SchemaGraphFactory { + + public SchemaGraph createGraph(GraphQLSchema schema) { + Set roots = new LinkedHashSet<>(); + roots.add(schema.getQueryType()); + if (schema.isSupportingMutations()) { + roots.add(schema.getMutationType()); + } + if (schema.isSupportingSubscriptions()) { + roots.add(schema.getSubscriptionType()); + } + roots.addAll(schema.getAdditionalTypes()); + roots.addAll(schema.getDirectives()); + roots.addAll(schema.getSchemaDirectives()); + roots.add(schema.getIntrospectionSchemaType()); + Traverser traverser = Traverser.depthFirst(GraphQLSchemaElement::getChildren); + SchemaGraph schemaGraph = new SchemaGraph(); + traverser.traverse(roots, new TraverserVisitor() { + @Override + public TraversalControl enter(TraverserContext context) { + if (context.thisNode() instanceof GraphQLObjectType) { + newObject((GraphQLObjectType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLInterfaceType) { + newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLUnionType) { + newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLScalarType) { + newScalar((GraphQLScalarType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLInputObjectType) { + newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLEnumType) { + newEnum((GraphQLEnumType) context.thisNode(), schemaGraph); + } + if (context.thisNode() instanceof GraphQLDirective) { + newDirective((GraphQLDirective) context.thisNode(), schemaGraph); + } + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl leave(TraverserContext context) { + return TraversalControl.CONTINUE; + } + }); + + for (Vertex vertex : schemaGraph.getVertices()) { + if ("Object".equals(vertex.getType())) { + handleObjectVertex(vertex, schemaGraph, schema); + } + if ("Interface".equals(vertex.getType())) { + handleInterfaceVertex(vertex, schemaGraph, schema); + } + if ("Union".equals(vertex.getType())) { + handleUnion(vertex, schemaGraph, schema); + } + if ("InputObject".equals(vertex.getType())) { + handleInputObject(vertex, schemaGraph, schema); + } + if ("AppliedDirective".equals(vertex.getType())) { + handleAppliedDirective(vertex, schemaGraph, schema); + } + } + return schemaGraph; + } + + private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); + schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); + } + + private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) graphQLSchema.getType(inputObject.get("name")); + List inputFields = inputObjectType.getFields(); + for (GraphQLInputObjectField inputField : inputFields) { + Vertex inputFieldVertex = schemaGraph.findTargetVertex(inputObject, vertex -> vertex.getType().equals("InputField") && + vertex.get("name").equals(inputField.getName())).get(); + handleInputField(inputFieldVertex, inputField, schemaGraph, graphQLSchema); + } + } + + private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField inputField, SchemaGraph + schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInputType type = inputField.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(inputFieldVertex, typeVertex); + typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + } + + private void handleUnion(Vertex unionVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLUnionType unionType = (GraphQLUnionType) graphQLSchema.getType(unionVertex.get("name")); + List types = unionType.getTypes(); + for (GraphQLNamedOutputType unionMemberType : types) { + Vertex unionMemberVertex = assertNotNull(schemaGraph.getType(unionMemberType.getName())); + schemaGraph.addEdge(new Edge(unionVertex, unionMemberVertex)); + } + } + + private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLInterfaceType interfaceType = (GraphQLInterfaceType) graphQLSchema.getType(interfaceVertex.get("name")); + + for (GraphQLNamedOutputType implementsInterface : interfaceType.getInterfaces()) { + Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName())); + schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex)); + } + + List fieldDefinitions = interfaceType.getFieldDefinitions(); + for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { + Vertex fieldVertex = schemaGraph.findTargetVertex(interfaceVertex, vertex -> vertex.getType().equals("Field") && + vertex.get("name").equals(fieldDefinition.getName())).get(); + handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); + } + + } + + private void handleObjectVertex(Vertex objectVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLObjectType objectType = graphQLSchema.getObjectType(objectVertex.get("name")); + + for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { + Vertex interfaceVertex = assertNotNull(schemaGraph.getType(interfaceType.getName())); + schemaGraph.addEdge(new Edge(objectVertex, interfaceVertex)); + } + + List fieldDefinitions = objectType.getFieldDefinitions(); + for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { + Vertex fieldVertex = schemaGraph.findTargetVertex(objectVertex, vertex -> vertex.getType().equals("Field") && + vertex.get("name").equals(fieldDefinition.getName())).get(); + handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema); + } + } + + private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinition, SchemaGraph + schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLOutputType type = fieldDefinition.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(fieldVertex, typeVertex); + typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + + for (GraphQLArgument graphQLArgument : fieldDefinition.getArguments()) { + Vertex argumentVertex = schemaGraph.findTargetVertex(fieldVertex, vertex -> vertex.getType().equals("Argument") && + vertex.get("name").equals(graphQLArgument.getName())).get(); + handleArgument(argumentVertex, graphQLArgument, schemaGraph); + } + } + + private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { + GraphQLInputType type = graphQLArgument.getType(); + GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); + Edge typeEdge = new Edge(argumentVertex, typeVertex); + typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + schemaGraph.addEdge(typeEdge); + } + + private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph) { + Vertex objectVertex = new Vertex("Object"); + objectVertex.add("name", graphQLObjectType.getName()); + objectVertex.add("description", graphQLObjectType.getDescription()); + for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + schemaGraph.addVertex(newFieldVertex); + schemaGraph.addEdge(new Edge(objectVertex, newFieldVertex)); + } + schemaGraph.addVertex(objectVertex); + schemaGraph.addType(graphQLObjectType.getName(), objectVertex); + cratedAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); + } + + private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph) { + Vertex fieldVertex = new Vertex("Field"); + fieldVertex.add("name", graphQLFieldDefinition.getName()); + fieldVertex.add("description", graphQLFieldDefinition.getDescription()); + for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { + Vertex argumentVertex = newArgument(argument, schemaGraph); + schemaGraph.addVertex(argumentVertex); + schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex)); + } + cratedAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph); + return fieldVertex; + } + + private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { + Vertex vertex = new Vertex("Argument"); + vertex.add("name", graphQLArgument.getName()); + vertex.add("description", graphQLArgument.getDescription()); + cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); + return vertex; + } + + private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { + Vertex scalarVertex = new Vertex("Scalar"); + scalarVertex.add("name", scalarType.getName()); + scalarVertex.add("description", scalarType.getDescription()); + schemaGraph.addVertex(scalarVertex); + schemaGraph.addType(scalarType.getName(), scalarVertex); + cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); + } + + private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph) { + Vertex interfaceVertex = new Vertex("Interface"); + interfaceVertex.add("name", interfaceType.getName()); + interfaceVertex.add("description", interfaceType.getDescription()); + for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + schemaGraph.addVertex(newFieldVertex); + schemaGraph.addEdge(new Edge(interfaceVertex, newFieldVertex)); + } + schemaGraph.addVertex(interfaceVertex); + schemaGraph.addType(interfaceType.getName(), interfaceVertex); + cratedAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); + } + + private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { + Vertex enumVertex = new Vertex("Enum"); + enumVertex.add("name", enumType.getName()); + enumVertex.add("description", enumType.getDescription()); + for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { + Vertex enumValueVertex = new Vertex("EnumValue"); + enumValueVertex.add("name", enumValue.getName()); + schemaGraph.addVertex(enumValueVertex); + schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); + cratedAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph); + } + schemaGraph.addVertex(enumVertex); + schemaGraph.addType(enumType.getName(), enumVertex); + cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); + } + + private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { + Vertex unionVertex = new Vertex("Union"); + unionVertex.add("name", unionType.getName()); + unionVertex.add("description", unionType.getDescription()); + schemaGraph.addVertex(unionVertex); + schemaGraph.addType(unionType.getName(), unionVertex); + cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); + } + + private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph) { + Vertex inputObjectVertex = new Vertex("InputObject"); + inputObjectVertex.add("name", inputObject.getName()); + inputObjectVertex.add("description", inputObject.getDescription()); + for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { + Vertex newInputField = newInputField(inputObjectField, schemaGraph); + Edge newEdge = new Edge(inputObjectVertex, newInputField); + schemaGraph.addEdge(newEdge); + cratedAppliedDirectives(inputObjectVertex, inputObjectField.getDirectives(), schemaGraph); + } + schemaGraph.addVertex(inputObjectVertex); + schemaGraph.addType(inputObject.getName(), inputObjectVertex); + cratedAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph); + } + + private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph + schemaGraph) { + for (GraphQLDirective appliedDirective : appliedDirectives) { + Vertex appliedDirectiveVertex = new Vertex("AppliedDirective"); + appliedDirectiveVertex.add("name", appliedDirective.getName()); + for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { + Vertex appliedArgumentVertex = new Vertex("AppliedArgument"); + appliedArgumentVertex.add("name", appliedArgument.getName()); + appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); + schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); + } + schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); + } + } + + private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { + Vertex directiveVertex = new Vertex("Directive"); + directiveVertex.add("name", directive.getName()); + directiveVertex.add("description", directive.getDescription()); + for (GraphQLArgument argument : directive.getArguments()) { + Vertex argumentVertex = newArgument(argument, schemaGraph); + schemaGraph.addVertex(argumentVertex); + schemaGraph.addEdge(new Edge(directiveVertex, argumentVertex)); + } + schemaGraph.addDirective(directive.getName(), directiveVertex); + schemaGraph.addVertex(directiveVertex); + } + + private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph) { + Vertex vertex = new Vertex("InputField"); + vertex.add("name", inputField.getName()); + vertex.add("description", inputField.getDescription()); + cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); + return vertex; + } + +} diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 28b3a2d5f2..c34c1553cf 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -22,4 +22,8 @@ public String getType() { public T get(String propName) { return (T) properties.get(propName); } + + public Map getProperties() { + return properties; + } } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index b65bb81591..fd8a5b58d9 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -22,4 +22,25 @@ class SchemaDiffingTest extends Specification { schemaGraph.size() == 64 } + + def "test ged"() { + given: + def schema1 = schema(""" + type Query { + hello: String + } + """) + def schema2 = schema(""" + type Query { + hello2: String + } + """) + + when: + new SchemaDiffing().diff(schema1, schema2) + + then: + true + + } } From 7ff95781a2579533a89a60d4451e23a2a658c692 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 16 Nov 2021 15:06:12 +1100 Subject: [PATCH 003/294] wip --- .../java/graphql/schema/diffing/Edge.java | 39 ++-- .../graphql/schema/diffing/EditOperation.java | 23 +++ .../java/graphql/schema/diffing/Mapping.java | 92 +++++++++ .../graphql/schema/diffing/SchemaDiffing.java | 175 ++++++++++-------- .../graphql/schema/diffing/SchemaGraph.java | 12 +- .../schema/diffing/SchemaGraphFactory.java | 14 +- .../java/graphql/schema/diffing/Vertex.java | 19 +- .../schema/diffing/SchemaDiffingTest.groovy | 103 ++++++++++- 8 files changed, 377 insertions(+), 100 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/EditOperation.java create mode 100644 src/main/java/graphql/schema/diffing/Mapping.java diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index e25261139e..7aeea521ed 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -1,30 +1,36 @@ package graphql.schema.diffing; public class Edge { - private Vertex from; - private Vertex to; + private Vertex one; + private Vertex two; private String label = ""; public Edge(Vertex from, Vertex to) { - this.from = from; - this.to = to; + this.one = from; + this.two = to; } - public Vertex getFrom() { - return from; + public Edge(Vertex from, Vertex to, String label) { + this.one = from; + this.two = to; + this.label = label; + } + + public Vertex getOne() { + return one; } - public void setFrom(Vertex from) { - this.from = from; + public void setOne(Vertex one) { + this.one = one; } - public Vertex getTo() { - return to; + public Vertex getTwo() { + return two; } - public void setTo(Vertex to) { - this.to = to; + public void setTwo(Vertex two) { + this.two = two; } @@ -35,4 +41,13 @@ public void setLabel(String label) { public String getLabel() { return label; } + + @Override + public String toString() { + return "Edge{" + + "one=" + one + + ", two=" + two + + ", label='" + label + '\'' + + '}'; + } } diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java new file mode 100644 index 0000000000..b9ab05c30c --- /dev/null +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -0,0 +1,23 @@ +package graphql.schema.diffing; + +public class EditOperation { + public EditOperation(Operation operation, String detail) { + this.operation = operation; + this.detail = detail; + } + + private Operation operation; + private String detail; + + enum Operation { + CHANGE_VERTEX, DELETE_VERTEX, INSERT_VERTEX, CHANGE_EDGE, INSERT_EDGE, DELETE_EDGE + } + + @Override + public String toString() { + return "EditOperation{" + + "operation=" + operation + + ", detail='" + detail + '\'' + + '}'; + } +} diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java new file mode 100644 index 0000000000..5748055b74 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -0,0 +1,92 @@ +package graphql.schema.diffing; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import graphql.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class Mapping { + private BiMap map = HashBiMap.create(); + private List sourceList = new ArrayList<>(); + private List targetList = new ArrayList<>(); + + private Mapping(BiMap map, List sourceList, List targetList) { + this.map = map; + this.sourceList = sourceList; + this.targetList = targetList; + } + + public Mapping() { + + } + + public Vertex getSource(Vertex target) { + return map.inverse().get(target); + } + + public Vertex getTarget(Vertex source) { + return map.get(source); + } + + public Vertex getSource(int i) { + return sourceList.get(i); + } + + public Vertex getTarget(int i) { + return targetList.get(i); + } + + public List getTargets() { + return targetList; + } + + public List getSources() { + return sourceList; + } + + public boolean containsSource(Vertex sourceVertex) { + return map.containsKey(sourceVertex); + } + + public boolean containsTarget(Vertex targetVertex) { + return map.containsValue(targetVertex); + } + + public int size() { + return map.size(); + } + + public void add(Vertex source, Vertex target) { + this.map.put(source, target); + this.sourceList.add(source); + this.targetList.add(target); + } + + public Mapping removeLastElement() { + HashBiMap newMap = HashBiMap.create(map); + newMap.remove(this.sourceList.get(this.sourceList.size() - 1)); + List newSourceList = new ArrayList<>(this.sourceList.subList(0, this.sourceList.size() - 1)); + List newTargetList = new ArrayList<>(this.targetList.subList(0, this.targetList.size() - 1)); + return new Mapping(newMap, newSourceList, newTargetList); + } + + public Mapping copy() { + HashBiMap newMap = HashBiMap.create(map); + List newSourceList = new ArrayList<>(this.sourceList); + List newTargetList = new ArrayList<>(this.targetList); + return new Mapping(newMap, newSourceList, newTargetList); + } + + public Mapping extendMapping(Vertex source, Vertex target) { + HashBiMap newMap = HashBiMap.create(map); + newMap.put(source, target); + List newSourceList = new ArrayList<>(this.sourceList); + newSourceList.add(source); + List newTargetList = new ArrayList<>(this.targetList); + newTargetList.add(target); + return new Mapping(newMap, newSourceList, newTargetList); + } +} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1cffbfe020..568637c9a8 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -5,7 +5,6 @@ import graphql.Assert; import graphql.collect.ImmutableKit; import graphql.schema.*; -import graphql.util.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; @@ -15,15 +14,13 @@ public class SchemaDiffing { - private static class Mapping { - } - private static class MappingEntry { - public MappingEntry(ImmutableList partialMapping, int level, double lowerBoundCost, ImmutableList candidates) { + + public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, List availableSiblings) { this.partialMapping = partialMapping; this.level = level; this.lowerBoundCost = lowerBoundCost; - this.candidates = candidates; + this.availableSiblings = availableSiblings; } public MappingEntry() { @@ -31,21 +28,27 @@ public MappingEntry() { } // target vertices which the fist `level` vertices of source graph are mapped to - ImmutableList partialMapping = ImmutableList.builder().build(); + Mapping partialMapping = new Mapping(); int level; double lowerBoundCost; - ImmutableList candidates = ImmutableList.builder().build(); + List availableSiblings = new ArrayList<>(); } - public void diff(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + public void diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { SchemaGraph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); SchemaGraph targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); + diffImpl(sourceGraph, targetGraph); + + } + + void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // we assert here that the graphs have the same size. The algorithm depends on it assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); - AtomicReference> result = new AtomicReference<>(); + AtomicReference bestFullMapping = new AtomicReference<>(); + AtomicReference> bestEdit = new AtomicReference<>(); PriorityQueue queue = new PriorityQueue((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); if (compareResult == 0) { @@ -55,47 +58,53 @@ public void diff(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { } }); queue.add(new MappingEntry()); + int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); + System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter)); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + System.out.println("skipping!"); continue; } // generate sibling - if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { + if (mappingEntry.level > 0 && mappingEntry.availableSiblings.size() > 0) { // we need to remove the last mapping - ImmutableList parentMapping = mappingEntry.partialMapping.subList(0, mappingEntry.partialMapping.size() - 1); - genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, result, sourceGraph, targetGraph); + Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); + genNextMapping(parentMapping, mappingEntry.level, mappingEntry.availableSiblings, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); } // generate children if (mappingEntry.level < graphSize) { // candidates are the vertices in target, of which are not used yet in partialMapping List childCandidates = new ArrayList<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping); - genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, ImmutableList.copyOf(childCandidates), queue, upperBoundCost, result, sourceGraph, targetGraph); + childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); + genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); } } System.out.println("ged cost: " + upperBoundCost.doubleValue()); - System.out.println("mapping : " + result); + System.out.println("edit : " + bestEdit); } // level starts at 1 indicating the level in the search tree to look for the next mapping - private void genNextMapping(ImmutableList partialMappingTargetList, + private void genNextMapping(Mapping partialMapping, int level, - ImmutableList candidates, + List candidates, PriorityQueue queue, AtomicDouble upperBound, - AtomicReference> bestMapping, + AtomicReference bestMapping, + AtomicReference> bestEdit, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + assertTrue(level - 1 == partialMapping.size()); List sourceList = sourceGraph.getVertices(); List targetList = targetGraph.getVertices(); ArrayList availableTargetVertices = new ArrayList<>(targetList); - availableTargetVertices.removeAll(partialMappingTargetList); - Vertex v_i = sourceList.get(level); + availableTargetVertices.removeAll(partialMapping.getTargets()); + assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); + // level starts at 1 ... therefore level - 1 is the current one we want to extend + Vertex v_i = sourceList.get(level - 1); int costMatrixSize = sourceList.size() - level + 1; double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - List partialMappingSourceList = new ArrayList<>(partialMappingTargetList.subList(0, level - 1)); // we are skipping the first level -i indeces for (int i = level - 1; i < sourceList.size(); i++) { @@ -105,7 +114,7 @@ private void genNextMapping(ImmutableList partialMappingTargetList, if (v == v_i && !candidates.contains(u)) { costMatrix[i - level + 1][j] = Integer.MAX_VALUE; } else { - costMatrix[i - level + 1][j] = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMappingSourceList, partialMappingTargetList); + costMatrix[i - level + 1][j] = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMapping.getTargets()); } j++; } @@ -114,74 +123,90 @@ private void genNextMapping(ImmutableList partialMappingTargetList, // find out the best extension HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int[] assignments = hungarianAlgorithm.execute(); - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = targetList.get(v_i_target_Index); - double bestExtensionLowerBound = costMatrix[0][v_i_target_Index]; - if (bestExtensionLowerBound < upperBound.doubleValue()) { - ImmutableList newMapping = ImmutableKit.addToList(partialMappingTargetList, bestExtensionTargetVertex); - ImmutableList newCandidates = removeVertex(candidates, bestExtensionTargetVertex); - queue.add(new MappingEntry(newMapping, level, bestExtensionLowerBound, newCandidates)); + // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i + int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); + double costMatrixSum = 0; + for(int i = 0; i < assignments.length; i++) { + costMatrixSum += costMatrix[i][assignments[i]]; + } + double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; + + if (lowerBoundForPartialMapping < upperBound.doubleValue()) { + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); + candidates.remove(bestExtensionTargetVertex); + queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); // we have a full mapping from the cost matrix - List fullMapping = new ArrayList<>(partialMappingTargetList); + Mapping fullMapping = partialMapping.copy(); for (int i = 0; i < assignments.length; i++) { - fullMapping.add(availableTargetVertices.get(assignments[i])); + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + } + assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit: " + bestEdit); + } else { +// System.out.println("to expensive cost for overall mapping " + costForFullMapping); } - // the cost of the full mapping is the new upperBound (aka the current best mapping) - upperBound.set(editorialCostForFullMapping(fullMapping, sourceGraph, targetGraph)); - bestMapping.set(fullMapping); + }else { + System.out.println("don't add new entries "); } } // minimum number of edit operations for a full mapping - private int editorialCostForFullMapping(List fullMapping, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph sourceGraph, SchemaGraph targetGraph, List editOperationsResult) { int cost = 0; - int i = 0; - for (Vertex v : sourceGraph.getVertices()) { - Vertex targetVertex = fullMapping.get(i++); + for (int i = 0; i < partialOrFullMapping.size(); i++) { + Vertex sourceVertex = partialOrFullMapping.getSource(i); + Vertex targetVertex = partialOrFullMapping.getTarget(i); // Vertex changing (relabeling) - boolean equalNodes = v.getType().equals(targetVertex.getType()) && v.getProperties().equals(targetVertex.getProperties()); + boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); if (!equalNodes) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex)); cost++; } - List edges = sourceGraph.getEdges(v); - // edge deletion or relabeling - for (Edge sourceEdge : edges) { - Vertex targetFrom = getTargetVertex(fullMapping, sourceEdge.getFrom(), sourceGraph); - Vertex targetTo = getTargetVertex(fullMapping, sourceEdge.getTo(), sourceGraph); - Edge targetEdge = targetGraph.getEdge(targetFrom, targetTo); - if (targetEdge == null || !sourceEdge.getLabel().equals(targetEdge.getLabel())) { - cost++; - } + } + List subGraphSource = sourceGraph.getVertices().subList(0, partialOrFullMapping.size()); + List edges = sourceGraph.getEdges(); + // edge deletion or relabeling + for (Edge sourceEdge : edges) { + // only edges relevant to the subgraph + if (!subGraphSource.contains(sourceEdge.getOne()) || !subGraphSource.contains(sourceEdge.getTwo())) { + continue; } - - // edge insertion - for (Edge targetEdge : targetGraph.getEdges()) { - Vertex sourceFrom = getSourceVertex(fullMapping, targetEdge.getFrom(), sourceGraph); - Vertex sourceTo = getSourceVertex(fullMapping, targetEdge.getTo(), sourceGraph); - if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { - cost++; - } + Vertex target1 = partialOrFullMapping.getTarget(sourceEdge.getOne()); + Vertex target2 = partialOrFullMapping.getTarget(sourceEdge.getTwo()); + Edge targetEdge = targetGraph.getEdge(target1, target2); + if (targetEdge == null) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_EDGE, "Delete edge " + sourceEdge)); + cost++; + } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_EDGE, "Change " + sourceEdge + " to " + targetEdge)); + cost++; } } - return cost; - } - // TODO: very inefficient - private Vertex getTargetVertex(List mappingTargetVertices, Vertex sourceVertex, SchemaGraph sourceGraph) { - for (int i = 0; i < sourceGraph.getVertices().size(); i++) { - Vertex v = sourceGraph.getVertices().get(i); - if (v != sourceVertex) continue; - return mappingTargetVertices.get(i); + // edge insertion + for (Edge targetEdge : targetGraph.getEdges()) { + // only subraph edges + if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { + continue; + } + Vertex sourceFrom = partialOrFullMapping.getSource(targetEdge.getOne()); + Vertex sourceTo = partialOrFullMapping.getSource(targetEdge.getTwo()); + if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_EDGE, "Insert edge " + targetEdge)); + cost++; + } } - return Assert.assertShouldNeverHappen(); - } - - private Vertex getSourceVertex(List mappingTargetVertices, Vertex targetVertex, SchemaGraph sourceGraph) { - int index = mappingTargetVertices.indexOf(targetVertex); - assertTrue(index > -1); - return Assert.assertNotNull(sourceGraph.getVertices().get(index)); + return cost; } // lower bound mapping cost between for v -> u in respect to a partial mapping @@ -200,7 +225,8 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset multisetLabelsV = HashMultiset.create(); for (Edge edge : adjacentEdgesV) { - if (nonMappedSourceVertices.contains(edge.getFrom()) && nonMappedSourceVertices.contains(edge.getTo())) { + // test if this an inner edge + if (nonMappedSourceVertices.contains(edge.getOne()) && nonMappedSourceVertices.contains(edge.getTwo())) { multisetLabelsV.add(edge.getLabel()); } } @@ -208,7 +234,8 @@ private double calcLowerBoundMappingCost(Vertex v, Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { - if (nonMappedTargetVertices.contains(edge.getFrom()) && nonMappedTargetVertices.contains(edge.getTo())) { + // test if this is an inner edge + if (nonMappedTargetVertices.contains(edge.getOne()) && nonMappedTargetVertices.contains(edge.getTwo())) { multisetLabelsU.add(edge.getLabel()); } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 1f2605c7f5..7309b08e0b 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -12,7 +12,6 @@ public class SchemaGraph { private List vertices = new ArrayList<>(); - private Map> fromEdges = new LinkedHashMap<>(); private List edges = new ArrayList<>(); private Map typesByName = new LinkedHashMap<>(); @@ -25,12 +24,12 @@ public void addVertex(Vertex vertex) { public void addEdge(Edge edge) { edges.add(edge); - fromEdges.computeIfAbsent(edge.getFrom(), ignored -> new ArrayList<>()).add(edge); - edgeByVertexPair.put(edge.getFrom(), edge.getTo(), edge); + edgeByVertexPair.put(edge.getOne(), edge.getTwo(), edge); + edgeByVertexPair.put(edge.getTwo(), edge.getOne(), edge); } public List getEdges(Vertex from) { - return fromEdges.getOrDefault(from, Collections.emptyList()); + return new ArrayList<>(edgeByVertexPair.row(from).values()); } public List getEdges() { @@ -41,9 +40,6 @@ public List getEdges() { return edgeByVertexPair.get(from, to); } - public List getTargetVertices(Vertex from) { - return ImmutableKit.map(fromEdges.get(from), Edge::getTo); - } public List getVertices() { return vertices; @@ -66,7 +62,7 @@ public Vertex getDirective(String name) { } public Optional findTargetVertex(Vertex from, Predicate vertexPredicate) { - return fromEdges.get(from).stream().map(Edge::getTo).filter(vertexPredicate).findFirst(); + return edgeByVertexPair.row(from).values().stream().map(Edge::getTwo).filter(vertexPredicate).findFirst(); } public int size() { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index c1f627dc8e..1155f0a52e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -6,6 +6,7 @@ import graphql.util.TraverserContext; import graphql.util.TraverserVisitor; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -62,7 +63,8 @@ public TraversalControl leave(TraverserContext context) { } }); - for (Vertex vertex : schemaGraph.getVertices()) { + ArrayList copyOfVertices = new ArrayList<>(schemaGraph.getVertices()); + for (Vertex vertex : copyOfVertices) { if ("Object".equals(vertex.getType())) { handleObjectVertex(vertex, schemaGraph, schema); } @@ -101,8 +103,11 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex dummyTypeVertex = new Vertex("type"); + schemaGraph.addVertex(dummyTypeVertex); + schemaGraph.addEdge(new Edge(inputFieldVertex,dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(inputFieldVertex, typeVertex); + Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); schemaGraph.addEdge(typeEdge); } @@ -153,8 +158,11 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); + Vertex dummyTypeVertex = new Vertex("type"); + schemaGraph.addVertex(dummyTypeVertex); + schemaGraph.addEdge(new Edge(fieldVertex,dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(fieldVertex, typeVertex); + Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); schemaGraph.addEdge(typeEdge); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index c34c1553cf..00a45925c9 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -7,23 +7,38 @@ public class Vertex { private String type; private Map properties = new LinkedHashMap<>(); + private String name; public Vertex(String type) { this.type = type; } + public Vertex(String type, String name) { + this.type = type; + this.name = name; + } public void add(String propName, Object propValue) { - properties.put(propName,propValue); + properties.put(propName, propValue); } public String getType() { return type; } + public T get(String propName) { - return (T) properties.get(propName); + return (T) properties.get(propName); } public Map getProperties() { return properties; } + + @Override + public String toString() { + return "Vertex{" + + "type='" + type + '\'' + + ", properties=" + properties + + ", name='" + name + '\'' + + '}'; + } } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index fd8a5b58d9..d1a7704676 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -37,10 +37,111 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diff(schema1, schema2) + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: true } + + def "test ged2"() { + given: + def schema1 = schema(""" + type Query { + hello: Foo + } + type Foo { + foo: String + } + """) + def schema2 = schema(""" + type Query { + hello2: Foo2 + } + type Foo2 { + foo2: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "delete a field ged2"() { + given: + def schema1 = schema(""" + type Query { + hello: String + toDelete: String + } + """) + def schema2 = schema(""" + type Query { + hello: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "test example schema"() { + given: + def source = buildSourceGraph() + def target = buildTargetGraph() + when: + new SchemaDiffing().diffImpl(source, target) + then: + true + } + + SchemaGraph buildTargetGraph() { + SchemaGraph targetGraph = new SchemaGraph(); + def a_1 = new Vertex("A", "u1") + def d = new Vertex("D", "u2") + def a_2 = new Vertex("A", "u3") + def a_3 = new Vertex("A", "u4") + def e = new Vertex("E", "u5") + targetGraph.addVertex(a_1); + targetGraph.addVertex(d); + targetGraph.addVertex(a_2); + targetGraph.addVertex(a_3); + targetGraph.addVertex(e); + + targetGraph.addEdge(new Edge(a_1, d, "a")) + targetGraph.addEdge(new Edge(d, a_2, "a")) + targetGraph.addEdge(new Edge(a_2, a_3, "a")) + targetGraph.addEdge(new Edge(a_3, e, "a")) + targetGraph + + } + + SchemaGraph buildSourceGraph() { + SchemaGraph sourceGraph = new SchemaGraph(); + def c = new Vertex("C", "v5") + def a_1 = new Vertex("A", "v1") + def a_2 = new Vertex("A", "v2") + def a_3 = new Vertex("A", "v3") + def b = new Vertex("B", "v4") + sourceGraph.addVertex(a_1); + sourceGraph.addVertex(a_2); + sourceGraph.addVertex(a_3); + sourceGraph.addVertex(b); + sourceGraph.addVertex(c); + + sourceGraph.addEdge(new Edge(c, a_1, "b")) + sourceGraph.addEdge(new Edge(a_1, a_2, "a")) + sourceGraph.addEdge(new Edge(a_2, a_3, "a")) + sourceGraph.addEdge(new Edge(a_3, b, "a")) + sourceGraph + + } } From a718a91dccbe0d7218f2bf9ed0946fdfc4b71c5c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 16 Nov 2021 16:57:11 +1100 Subject: [PATCH 004/294] wip --- .../graphql/schema/diffing/GraphPrinter.java | 23 ++++ .../graphql/schema/diffing/SchemaDiffing.java | 17 ++- .../graphql/schema/diffing/SchemaGraph.java | 19 +++ .../java/graphql/schema/diffing/Vertex.java | 9 +- .../graphql/schema/diffing/dot/Dotfile.java | 124 ++++++++++++++++++ .../schema/diffing/SchemaDiffingTest.groovy | 37 +++++- 6 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/GraphPrinter.java create mode 100644 src/main/java/graphql/schema/diffing/dot/Dotfile.java diff --git a/src/main/java/graphql/schema/diffing/GraphPrinter.java b/src/main/java/graphql/schema/diffing/GraphPrinter.java new file mode 100644 index 0000000000..d09e67244c --- /dev/null +++ b/src/main/java/graphql/schema/diffing/GraphPrinter.java @@ -0,0 +1,23 @@ +package graphql.schema.diffing; + +import graphql.schema.diffing.dot.Dotfile; + +public class GraphPrinter { + + public static String print(SchemaGraph schemaGraph) { + Dotfile dotfile = new Dotfile(); + for (Vertex vertex : schemaGraph.getVertices()) { + String name = vertex.get("name"); + if (name == null) { + name = vertex.getType(); + } + dotfile.addNode("V" + Integer.toHexString(vertex.hashCode()), name, "blue"); + } + for (Edge edge : schemaGraph.getEdges()) { + String nameOne = edge.getOne().get("name"); + String nameTwo = edge.getTwo().get("name"); + dotfile.addEdge("V" + Integer.toHexString(edge.getOne().hashCode()), "V" + Integer.toHexString(edge.getTwo().hashCode()), edge.getLabel()); + } + return dotfile.print(); + } +} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 568637c9a8..f7bb983443 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -37,12 +37,18 @@ public MappingEntry() { public void diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { SchemaGraph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); SchemaGraph targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); +// System.out.println(GraphPrinter.print(sourceGraph)); diffImpl(sourceGraph, targetGraph); } void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // we assert here that the graphs have the same size. The algorithm depends on it + if (sourceGraph.size() < targetGraph.size()) { + sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); + } else if (sourceGraph.size() > targetGraph.size()) { + targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size()); + } assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); @@ -61,7 +67,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); - System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter)); + System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " q size: " + queue.size()); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { System.out.println("skipping!"); continue; @@ -127,8 +133,8 @@ private void genNextMapping(Mapping partialMapping, // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); double costMatrixSum = 0; - for(int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i][assignments[i]]; + for (int i = 0; i < assignments.length; i++) { + costMatrixSum += costMatrix[i][assignments[i]]; } double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; @@ -137,6 +143,7 @@ private void genNextMapping(Mapping partialMapping, Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); candidates.remove(bestExtensionTargetVertex); + System.out.println("adding new entry at level " + level + " with candidates: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); // we have a full mapping from the cost matrix @@ -151,11 +158,11 @@ private void genNextMapping(Mapping partialMapping, upperBound.set(costForFullMapping); bestMapping.set(fullMapping); bestEdit.set(editOperations); - System.out.println("setting new best edit: " + bestEdit); + System.out.println("setting new best edit with size " + editOperations.size()); } else { // System.out.println("to expensive cost for overall mapping " + costForFullMapping); } - }else { + } else { System.out.println("don't add new entries "); } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 7309b08e0b..7b31ce8b88 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -2,6 +2,7 @@ import com.google.common.collect.HashBasedTable; +import com.google.common.collect.HashBiMap; import com.google.common.collect.Table; import graphql.collect.ImmutableKit; import org.jetbrains.annotations.Nullable; @@ -18,6 +19,16 @@ public class SchemaGraph { private Map directivesByName = new LinkedHashMap<>(); private Table edgeByVertexPair = HashBasedTable.create(); + public SchemaGraph() { + + } + + public SchemaGraph(List vertices, List edges, Table edgeByVertexPair) { + this.vertices = vertices; + this.edges = edges; + this.edgeByVertexPair = edgeByVertexPair; + } + public void addVertex(Vertex vertex) { vertices.add(vertex); } @@ -68,4 +79,12 @@ public Optional findTargetVertex(Vertex from, Predicate vertexPr public int size() { return vertices.size(); } + + public void addIsolatedVertices(int count) { + String uniqueType = String.valueOf(UUID.randomUUID()); + for (int i = 0; i < count; i++) { + Vertex isolatedVertex = new Vertex(uniqueType); + vertices.add(isolatedVertex); + } + } } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 00a45925c9..e4afaf7d98 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -7,16 +7,15 @@ public class Vertex { private String type; private Map properties = new LinkedHashMap<>(); - private String name; + private String debugName; public Vertex(String type) { this.type = type; } - public Vertex(String type, String name) { + public Vertex(String type, String debugName) { this.type = type; - this.name = name; + this.debugName = debugName; } - public void add(String propName, Object propValue) { properties.put(propName, propValue); } @@ -38,7 +37,7 @@ public String toString() { return "Vertex{" + "type='" + type + '\'' + ", properties=" + properties + - ", name='" + name + '\'' + + ", debugName='" + debugName + '\'' + '}'; } } diff --git a/src/main/java/graphql/schema/diffing/dot/Dotfile.java b/src/main/java/graphql/schema/diffing/dot/Dotfile.java new file mode 100644 index 0000000000..ac28a549bb --- /dev/null +++ b/src/main/java/graphql/schema/diffing/dot/Dotfile.java @@ -0,0 +1,124 @@ +package graphql.schema.diffing.dot; + +import java.util.ArrayList; +import java.util.List; + + +public class Dotfile { + + public static class Node { + public Node(String id, String label, String color) { + this.id = id; + this.label = label; + this.color = color; + } + + String id; + String label; + String color; + } + + public static class Edge { + public Edge(String from, String to, String label) { + this.from = from; + this.to = to; + this.label = label; + } + + String from; + String to; + String label; + } + + public static class SubGraph { + + String id; + String label; + List edges = new ArrayList<>(); + List nodes = new ArrayList<>(); + + public SubGraph(String id, String label) { + this.id = id; + this.label = label; + } + + public String getId() { + return id; + } + + public void addEdge(Edge e) { + edges.add(e); + } + + public void addNode(Node node) { + nodes.add(node); + } + + } + + + private List nodes = new ArrayList<>(); + private List edges = new ArrayList<>(); + private List subGraphs = new ArrayList<>(); + + + public void addNode(Node node) { + nodes.add(node); + } + + public void addNode(String id, String label, String color) { + nodes.add(new Node(id, label, color)); + } + + public void addEdge(String from, String to, String label) { + edges.add(new Edge(from, to, label)); + } + + public void addEdge(Edge e) { + edges.add(e); + } + + public void addSubgraph(SubGraph subGraph) { + subGraphs.add(subGraph); + } + + public String getId() { + return ""; + } + + public String print() { + StringBuilder result = new StringBuilder(); + result.append("graph G {\n"); + for (Node node : nodes) { + result.append(node.id).append("[label=\"").append(node.label).append("\" color=").append(node.color).append(" style=filled").append("];\n"); + } + for (Edge edge : edges) { + result.append(edge.from).append(" -- ").append(edge.to).append("[label=\"").append(edge.label).append("\"];\n"); + } + for (SubGraph subGraph : subGraphs) { + result.append("subgraph cluster_").append(subGraph.id).append("{\n").append("label=\"").append(subGraph.label).append("\";\n"); + for (Node node : subGraph.nodes) { + result.append(node.id).append("[label=\"").append(node.label).append("\" color=").append(node.color).append(" style=filled").append("];\n"); + } + for (Edge edge : subGraph.edges) { + result.append(edge.from).append(" -- ").append(edge.to).append("[label=\"").append(edge.label).append("\"];\n"); + } + result.append("}"); + + } +// result.append(explanation()); + result.append("}"); + return result.toString(); + } + + String explanation() { + return "subgraph cluster_explanation {\n" + + "label=\"Explanation\";\n" + + "concept [color=green style=filled label=\"Concept\"];\n" + + "orgEntity [color=lightblue style=filled label=\"Org Entity\"];\n" + + "product [color=red style=filled label=\"Product\"];\n" + + "concept -> product [style=invis];\n" + + "orgEntity -> concept [style=invis];\n" + + "}"; + } +} diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index d1a7704676..3b0c19643d 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -71,7 +71,7 @@ class SchemaDiffingTest extends Specification { } - def "delete a field ged2"() { + def "delete a field"() { given: def schema1 = schema(""" type Query { @@ -103,6 +103,41 @@ class SchemaDiffingTest extends Specification { true } + def "test example schema 2"() { + given: + def source = sourceGraph2() + def target = targetGraph2() + when: + new SchemaDiffing().diffImpl(source, target) + then: + true + } + + SchemaGraph sourceGraph2() { + def source = new SchemaGraph() + Vertex a = new Vertex("A") + source.addVertex(a) + Vertex b = new Vertex("B") + source.addVertex(b) + Vertex c = new Vertex("C") + source.addVertex(c) + Vertex d = new Vertex("D") + source.addVertex(d) + source.addEdge(new Edge(a, b)) + source.addEdge(new Edge(b, c)) + source.addEdge(new Edge(c, d)) + source + } + + SchemaGraph targetGraph2() { + def target = new SchemaGraph() + Vertex a = new Vertex("A") + Vertex d = new Vertex("D") + target.addVertex(a) + target.addVertex(d) + target + } + SchemaGraph buildTargetGraph() { SchemaGraph targetGraph = new SchemaGraph(); def a_1 = new Vertex("A", "u1") From 7652c681392a357f966ea80469b5b8d59b8f2d9e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 16 Nov 2021 23:02:18 +1100 Subject: [PATCH 005/294] delete field working --- .../java/graphql/schema/diffing/Mapping.java | 4 ++ .../graphql/schema/diffing/SchemaDiffing.java | 31 ++++++++++++---- .../schema/diffing/SchemaGraphFactory.java | 37 ++++++++++--------- .../java/graphql/schema/diffing/Vertex.java | 4 ++ 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java index 5748055b74..e22cabfd38 100644 --- a/src/main/java/graphql/schema/diffing/Mapping.java +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -89,4 +89,8 @@ public Mapping extendMapping(Vertex source, Vertex target) { newTargetList.add(target); return new Mapping(newMap, newSourceList, newTargetList); } + + public BiMap getMap() { + return map; + } } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index f7bb983443..f839ec7d32 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -67,7 +67,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); - System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " q size: " + queue.size()); +// System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " q size: " + queue.size()); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { System.out.println("skipping!"); continue; @@ -120,7 +120,9 @@ private void genNextMapping(Mapping partialMapping, if (v == v_i && !candidates.contains(u)) { costMatrix[i - level + 1][j] = Integer.MAX_VALUE; } else { - costMatrix[i - level + 1][j] = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMapping.getTargets()); + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMapping.getTargets()); + costMatrix[i - level + 1][j] = cost; +// System.out.println("lower bound cost for mapping " + v + " to " + u + " is " + cost + " with index " + (i - level + 1) + " => " + j); } j++; } @@ -143,7 +145,7 @@ private void genNextMapping(Mapping partialMapping, Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); candidates.remove(bestExtensionTargetVertex); - System.out.println("adding new entry at level " + level + " with candidates: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); +// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); // we have a full mapping from the cost matrix @@ -160,13 +162,24 @@ private void genNextMapping(Mapping partialMapping, bestEdit.set(editOperations); System.out.println("setting new best edit with size " + editOperations.size()); } else { -// System.out.println("to expensive cost for overall mapping " + costForFullMapping); +// System.out.println("to expensive cost for overall mapping " +); } } else { - System.out.println("don't add new entries "); + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); + System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); } } + private List getDebugMap(Mapping mapping) { + List result = new ArrayList<>(); + for (Map.Entry entry : mapping.getMap().entrySet()) { + result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); + } + return result; + } + // minimum number of edit operations for a full mapping private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph sourceGraph, SchemaGraph targetGraph, List editOperationsResult) { int cost = 0; @@ -202,7 +215,7 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so // edge insertion for (Edge targetEdge : targetGraph.getEdges()) { - // only subraph edges + // only subgraph edges if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { continue; } @@ -252,14 +265,16 @@ private double calcLowerBoundMappingCost(Vertex v, Vertex vPrime = partialMappingSourceList.get(i); Vertex mappedVPrime = partialMappingTargetList.get(i); Edge sourceEdge = sourceGraph.getEdge(v, vPrime); + String labelSourceEdge = sourceEdge != null ? sourceEdge.getLabel() : null; Edge targetEdge = targetGraph.getEdge(u, mappedVPrime); - if (sourceEdge != targetEdge) { + String labelTargetEdge = targetEdge != null ? targetEdge.getLabel() : null; + if (!Objects.equals(labelSourceEdge,labelTargetEdge)) { anchoredVerticesCost++; } } Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - +// System.out.println("equalNodes : " + (equalNodes ? 0 : 1) + " editDistance " + (multiSetEditDistance / 2.0) + " anchored cost" + (anchoredVerticesCost)); return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 1155f0a52e..6543a56dfd 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -15,6 +15,9 @@ public class SchemaGraphFactory { + public static final String DUMMY_TYPE_VERTICE = "__DUMMY_TYPE_VERTICE"; + private int counter = 1; + public SchemaGraph createGraph(GraphQLSchema schema) { Set roots = new LinkedHashSet<>(); roots.add(schema.getQueryType()); @@ -103,9 +106,9 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex("type"); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); schemaGraph.addVertex(dummyTypeVertex); - schemaGraph.addEdge(new Edge(inputFieldVertex,dummyTypeVertex)); + schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); @@ -158,9 +161,9 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex("type"); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); schemaGraph.addVertex(dummyTypeVertex); - schemaGraph.addEdge(new Edge(fieldVertex,dummyTypeVertex)); + schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); @@ -183,7 +186,7 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume } private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph) { - Vertex objectVertex = new Vertex("Object"); + Vertex objectVertex = new Vertex("Object", String.valueOf(counter++)); objectVertex.add("name", graphQLObjectType.getName()); objectVertex.add("description", graphQLObjectType.getDescription()); for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { @@ -197,7 +200,7 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr } private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph) { - Vertex fieldVertex = new Vertex("Field"); + Vertex fieldVertex = new Vertex("Field", String.valueOf(counter++)); fieldVertex.add("name", graphQLFieldDefinition.getName()); fieldVertex.add("description", graphQLFieldDefinition.getDescription()); for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { @@ -210,7 +213,7 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra } private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { - Vertex vertex = new Vertex("Argument"); + Vertex vertex = new Vertex("Argument", String.valueOf(counter++)); vertex.add("name", graphQLArgument.getName()); vertex.add("description", graphQLArgument.getDescription()); cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); @@ -218,7 +221,7 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr } private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { - Vertex scalarVertex = new Vertex("Scalar"); + Vertex scalarVertex = new Vertex("Scalar", String.valueOf(counter++)); scalarVertex.add("name", scalarType.getName()); scalarVertex.add("description", scalarType.getDescription()); schemaGraph.addVertex(scalarVertex); @@ -227,7 +230,7 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { } private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph) { - Vertex interfaceVertex = new Vertex("Interface"); + Vertex interfaceVertex = new Vertex("Interface", String.valueOf(counter++)); interfaceVertex.add("name", interfaceType.getName()); interfaceVertex.add("description", interfaceType.getDescription()); for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { @@ -241,11 +244,11 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema } private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { - Vertex enumVertex = new Vertex("Enum"); + Vertex enumVertex = new Vertex("Enum", String.valueOf(counter++)); enumVertex.add("name", enumType.getName()); enumVertex.add("description", enumType.getDescription()); for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { - Vertex enumValueVertex = new Vertex("EnumValue"); + Vertex enumValueVertex = new Vertex("EnumValue", String.valueOf(counter++)); enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); @@ -257,7 +260,7 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { } private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { - Vertex unionVertex = new Vertex("Union"); + Vertex unionVertex = new Vertex("Union", String.valueOf(counter++)); unionVertex.add("name", unionType.getName()); unionVertex.add("description", unionType.getDescription()); schemaGraph.addVertex(unionVertex); @@ -266,7 +269,7 @@ private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { } private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph) { - Vertex inputObjectVertex = new Vertex("InputObject"); + Vertex inputObjectVertex = new Vertex("InputObject", String.valueOf(counter++)); inputObjectVertex.add("name", inputObject.getName()); inputObjectVertex.add("description", inputObject.getDescription()); for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { @@ -283,10 +286,10 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph schemaGraph) { for (GraphQLDirective appliedDirective : appliedDirectives) { - Vertex appliedDirectiveVertex = new Vertex("AppliedDirective"); + Vertex appliedDirectiveVertex = new Vertex("AppliedDirective", String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { - Vertex appliedArgumentVertex = new Vertex("AppliedArgument"); + Vertex appliedArgumentVertex = new Vertex("AppliedArgument", String.valueOf(counter++)); appliedArgumentVertex.add("name", appliedArgument.getName()); appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); @@ -296,7 +299,7 @@ private void cratedAppliedDirectives(Vertex from, List applied } private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { - Vertex directiveVertex = new Vertex("Directive"); + Vertex directiveVertex = new Vertex("Directive", String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); directiveVertex.add("description", directive.getDescription()); for (GraphQLArgument argument : directive.getArguments()) { @@ -309,7 +312,7 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { } private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph) { - Vertex vertex = new Vertex("InputField"); + Vertex vertex = new Vertex("InputField", String.valueOf(counter++)); vertex.add("name", inputField.getName()); vertex.add("description", inputField.getDescription()); cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index e4afaf7d98..d562d04b55 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -32,6 +32,10 @@ public Map getProperties() { return properties; } + public String getDebugName() { + return debugName; + } + @Override public String toString() { return "Vertex{" + From 4c58b4c51615eed08d6454fb6019b201c3f8a60f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 17 Nov 2021 09:56:53 +1100 Subject: [PATCH 006/294] tests --- .../schema/diffing/SchemaDiffingTest.groovy | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 3b0c19643d..7baec60b00 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -7,7 +7,7 @@ import static graphql.TestUtil.schema class SchemaDiffingTest extends Specification { - def "test"() { + def "test schema generation"() { given: def schema = schema(""" type Query { @@ -23,7 +23,7 @@ class SchemaDiffingTest extends Specification { } - def "test ged"() { + def "test rename field"() { given: def schema1 = schema(""" type Query { @@ -44,7 +44,7 @@ class SchemaDiffingTest extends Specification { } - def "test ged2"() { + def "test two field renames one type rename"() { given: def schema1 = schema(""" type Query { @@ -71,6 +71,27 @@ class SchemaDiffingTest extends Specification { } + def "test field type change"() { + given: + def schema1 = schema(""" + type Query { + hello: Int + } + """) + def schema2 = schema(""" + type Query { + hello: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + def "delete a field"() { given: def schema1 = schema(""" From 7c50636bbc0528a7392463d5fd83a63020f64442 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 17 Nov 2021 17:36:35 +1100 Subject: [PATCH 007/294] tests --- .../schema/diffing/SchemaDiffingTest.groovy | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 7baec60b00..3872527dff 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -92,6 +92,108 @@ class SchemaDiffingTest extends Specification { } + def "change object type name used once"() { + given: + def schema1 = schema(""" + type Query { + hello: Foo + } + type Foo { + foo: String + } + """) + def schema2 = schema(""" + type Query { + hello: Foo2 + } + type Foo2 { + foo: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "change object type name used twice"() { + given: + def schema1 = schema(""" + type Query { + hello: Foo + hello2: Foo + } + type Foo { + foo: String + } + """) + def schema2 = schema(""" + type Query { + hello: Foo2 + hello2: Foo2 + } + type Foo2 { + foo: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "change directive not applied"() { + given: + def schema1 = schema(""" + directive @foo on FIELD_DEFINITION + type Query { + hello: String + } + """) + def schema2 = schema(""" + directive @foo2 on FIELD_DEFINITION + type Query { + hello: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "change directive which is also applied"() { + given: + def schema1 = schema(""" + directive @foo on FIELD_DEFINITION + type Query { + hello: String @foo + } + """) + def schema2 = schema(""" + directive @foo2 on FIELD_DEFINITION + type Query { + hello: String @foo2 + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + def "delete a field"() { given: def schema1 = schema(""" @@ -114,6 +216,28 @@ class SchemaDiffingTest extends Specification { } + def "add a field"() { + given: + def schema1 = schema(""" + type Query { + hello: String + } + """) + def schema2 = schema(""" + type Query { + hello: String + newField: String + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + def "test example schema"() { given: def source = buildSourceGraph() From 9f1a1f0c48c8a9ddc6b8014cd855b83f507ed37d Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 30 Nov 2021 09:38:49 +1100 Subject: [PATCH 008/294] wip --- .../java/graphql/schema/diffing/SchemaGraphFactory.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 6543a56dfd..5d05b22283 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -55,7 +55,10 @@ public TraversalControl enter(TraverserContext context) { newEnum((GraphQLEnumType) context.thisNode(), schemaGraph); } if (context.thisNode() instanceof GraphQLDirective) { - newDirective((GraphQLDirective) context.thisNode(), schemaGraph); + // only continue if not applied directive + if (context.getParentNode() == null) { + newDirective((GraphQLDirective) context.thisNode(), schemaGraph); + } } return TraversalControl.CONTINUE; } @@ -294,6 +297,7 @@ private void cratedAppliedDirectives(Vertex from, List applied appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); } + schemaGraph.addVertex(appliedDirectiveVertex); schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); } } From 8e928739bdf6b9e91b0caa8ea0823ddf95a0139c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 7 Dec 2021 10:05:51 +1100 Subject: [PATCH 009/294] optimizing a bit and testing large schemas --- .../graphql/schema/diffing/SchemaDiffing.java | 47 +++++++++--- .../schema/diffing/SchemaGraphFactory.java | 8 +-- .../schema/diffing/SchemaDiffingTest.groovy | 71 +++++++++++++++++++ 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index f839ec7d32..3ed66be5eb 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -51,7 +51,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { } assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); - + System.out.println("graph size: " + graphSize); AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); AtomicReference> bestEdit = new AtomicReference<>(); @@ -67,7 +67,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); -// System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " q size: " + queue.size()); + System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " queue size: " + queue.size()); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { System.out.println("skipping!"); continue; @@ -88,6 +88,9 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { } System.out.println("ged cost: " + upperBoundCost.doubleValue()); System.out.println("edit : " + bestEdit); + for (EditOperation editOperation : bestEdit.get()) { + System.out.println(editOperation); + } } // level starts at 1 indicating the level in the search tree to look for the next mapping @@ -113,6 +116,11 @@ private void genNextMapping(Mapping partialMapping, // we are skipping the first level -i indeces + int costCounter = 0; + int overallCount = (sourceList.size() - level) * availableTargetVertices.size(); + Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); + Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); + for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int j = 0; @@ -120,14 +128,15 @@ private void genNextMapping(Mapping partialMapping, if (v == v_i && !candidates.contains(u)) { costMatrix[i - level + 1][j] = Integer.MAX_VALUE; } else { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMapping.getTargets()); + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); costMatrix[i - level + 1][j] = cost; // System.out.println("lower bound cost for mapping " + v + " to " + u + " is " + cost + " with index " + (i - level + 1) + " => " + j); +// System.out.println("cost counter: " + (costCounter++) + "/" + overallCount + " percentage: " + (costCounter / (float) overallCount)); } j++; } } - + System.out.println("finished matrix"); // find out the best extension HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int[] assignments = hungarianAlgorithm.execute(); @@ -235,30 +244,42 @@ private double calcLowerBoundMappingCost(Vertex v, SchemaGraph sourceGraph, SchemaGraph targetGraph, List partialMappingSourceList, - List partialMappingTargetList) { + Set partialMappingSourceSet, + List partialMappingTargetList, + Set partialMappingTargetSet + ) { + long t = System.nanoTime(); boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges List adjacentEdgesV = sourceGraph.getEdges(v); - Set nonMappedSourceVertices = nonMappedVertices(sourceGraph.getVertices(), partialMappingSourceList); +// Set nonMappedSourceVertices = nonMappedVertices(sourceGraph.getVertices(), partialMappingSourceList); Multiset multisetLabelsV = HashMultiset.create(); for (Edge edge : adjacentEdgesV) { - // test if this an inner edge - if (nonMappedSourceVertices.contains(edge.getOne()) && nonMappedSourceVertices.contains(edge.getTwo())) { + // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices + // or: at least one edge is part of the partial mapping +// if (nonMappedSourceVertices.contains(edge.getOne()) && nonMappedSourceVertices.contains(edge.getTwo())) { + if (!partialMappingSourceSet.contains(edge.getOne()) || !partialMappingSourceSet.contains(edge.getTwo())) { multisetLabelsV.add(edge.getLabel()); } } + System.out.println("time1: " + (System.nanoTime() - t)); + long t2 = System.nanoTime(); + List adjacentEdgesU = targetGraph.getEdges(u); - Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); +// Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { // test if this is an inner edge - if (nonMappedTargetVertices.contains(edge.getOne()) && nonMappedTargetVertices.contains(edge.getTwo())) { + if (!partialMappingSourceSet.contains(edge.getOne()) || !partialMappingTargetSet.contains(edge.getTwo())) { multisetLabelsU.add(edge.getLabel()); } } + System.out.println("time2: " + (System.nanoTime() - t2)); + long t3 = System.nanoTime(); + int anchoredVerticesCost = 0; for (int i = 0; i < partialMappingSourceList.size(); i++) { @@ -268,13 +289,17 @@ private double calcLowerBoundMappingCost(Vertex v, String labelSourceEdge = sourceEdge != null ? sourceEdge.getLabel() : null; Edge targetEdge = targetGraph.getEdge(u, mappedVPrime); String labelTargetEdge = targetEdge != null ? targetEdge.getLabel() : null; - if (!Objects.equals(labelSourceEdge,labelTargetEdge)) { + if (!Objects.equals(labelSourceEdge, labelTargetEdge)) { anchoredVerticesCost++; } } + System.out.println("time3: " + (System.nanoTime() - t3)); + long t4 = System.nanoTime(); + Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); // System.out.println("equalNodes : " + (equalNodes ? 0 : 1) + " editDistance " + (multiSetEditDistance / 2.0) + " anchored cost" + (anchoredVerticesCost)); + System.out.println("time4: " + (System.nanoTime() - t4)); return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 5d05b22283..830d98162e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -132,7 +132,7 @@ private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGra for (GraphQLNamedOutputType implementsInterface : interfaceType.getInterfaces()) { Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName())); - schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex)); + schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex, "implements " + implementsInterface.getName())); } List fieldDefinitions = interfaceType.getFieldDefinitions(); @@ -147,9 +147,9 @@ private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGra private void handleObjectVertex(Vertex objectVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { GraphQLObjectType objectType = graphQLSchema.getObjectType(objectVertex.get("name")); - for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { - Vertex interfaceVertex = assertNotNull(schemaGraph.getType(interfaceType.getName())); - schemaGraph.addEdge(new Edge(objectVertex, interfaceVertex)); + for (GraphQLNamedOutputType implementsInterface : objectType.getInterfaces()) { + Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName())); + schemaGraph.addEdge(new Edge(objectVertex, implementsInterfaceVertex, "implements " + implementsInterface.getName())); } List fieldDefinitions = objectType.getFieldDefinitions(); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 3872527dff..080e4f4fb5 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1,5 +1,12 @@ package graphql.schema.diffing +import graphql.TestUtil +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLSchemaElement +import graphql.schema.GraphQLTypeVisitorStub +import graphql.schema.SchemaTransformer +import graphql.util.TraversalControl +import graphql.util.TraverserContext import spock.lang.Specification import static graphql.TestUtil.schema @@ -119,6 +126,70 @@ class SchemaDiffingTest extends Specification { } + def "remove Interface from Object"() { + given: + def schema1 = schema(""" + type Query { + hello: Foo + hello2: Foo2 + } + interface Node { + id: ID + } + type Foo implements Node{ + id: ID + } + type Foo2 implements Node{ + id: ID + } + """) + def schema2 = schema(""" + type Query { + hello: Foo + hello2: Foo2 + } + interface Node { + id: ID + } + type Foo implements Node{ + id: ID + } + type Foo2 { + id: ID + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "change large schema a bit"() { + given: + def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) + int counter = 0; + def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { + @Override + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { + if (fieldDefinition.getName() == "field50") { + counter++; + return changeNode(context, fieldDefinition.transform({ it.name("field50Changed") })) + } + return TraversalControl.CONTINUE + } + }) + println "changed fields: " + counter + when: + long t = System.currentTimeMillis() + new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) + println "time: " + (System.currentTimeMillis() - t) + then: + true + } + def "change object type name used twice"() { given: def schema1 = schema(""" From 81c366a972fcad8f0cb93ac8305d953562998ce2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 9 Dec 2021 11:57:14 +1100 Subject: [PATCH 010/294] wip --- .../graphql/schema/diffing/SchemaDiffing.java | 36 ++- .../graphql/schema/diffing/SchemaGraph.java | 4 +- .../java/graphql/schema/diffing/Vertex.java | 13 +- .../schema/diffing/SchemaDiffingTest.groovy | 239 ++++++++++++++++++ 4 files changed, 269 insertions(+), 23 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 3ed66be5eb..fa8b49a71b 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -2,8 +2,6 @@ import com.google.common.collect.*; import com.google.common.util.concurrent.AtomicDouble; -import graphql.Assert; -import graphql.collect.ImmutableKit; import graphql.schema.*; import java.util.*; @@ -34,15 +32,14 @@ public MappingEntry() { List availableSiblings = new ArrayList<>(); } - public void diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { SchemaGraph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); SchemaGraph targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); // System.out.println(GraphPrinter.print(sourceGraph)); - diffImpl(sourceGraph, targetGraph); - + return diffImpl(sourceGraph, targetGraph); } - void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // we assert here that the graphs have the same size. The algorithm depends on it if (sourceGraph.size() < targetGraph.size()) { sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); @@ -52,9 +49,11 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); + AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); AtomicReference> bestEdit = new AtomicReference<>(); + PriorityQueue queue = new PriorityQueue((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); if (compareResult == 0) { @@ -69,7 +68,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { MappingEntry mappingEntry = queue.poll(); System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " queue size: " + queue.size()); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { - System.out.println("skipping!"); +// System.out.println("skipping!"); continue; } // generate sibling @@ -91,6 +90,7 @@ void diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { for (EditOperation editOperation : bestEdit.get()) { System.out.println(editOperation); } + return bestEdit.get(); } // level starts at 1 indicating the level in the search tree to look for the next mapping @@ -136,7 +136,7 @@ private void genNextMapping(Mapping partialMapping, j++; } } - System.out.println("finished matrix"); +// System.out.println("finished matrix"); // find out the best extension HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int[] assignments = hungarianAlgorithm.execute(); @@ -169,7 +169,7 @@ private void genNextMapping(Mapping partialMapping, upperBound.set(costForFullMapping); bestMapping.set(fullMapping); bestEdit.set(editOperations); - System.out.println("setting new best edit with size " + editOperations.size()); + System.out.println("setting new best edit with size " + editOperations.size() + " at level " + level); } else { // System.out.println("to expensive cost for overall mapping " +); } @@ -177,7 +177,7 @@ private void genNextMapping(Mapping partialMapping, int v_i_target_Index = assignments[0]; Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); - System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); +// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); } } @@ -198,7 +198,13 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so // Vertex changing (relabeling) boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); if (!equalNodes) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex)); + if (sourceVertex.isArtificialNode()) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_VERTEX, "Insert" + targetVertex)); + } else if (targetVertex.isArtificialNode()) { + editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_VERTEX, "Delete " + sourceVertex)); + } else { + editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex)); + } cost++; } } @@ -248,7 +254,6 @@ private double calcLowerBoundMappingCost(Vertex v, List partialMappingTargetList, Set partialMappingTargetSet ) { - long t = System.nanoTime(); boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges @@ -265,8 +270,6 @@ private double calcLowerBoundMappingCost(Vertex v, multisetLabelsV.add(edge.getLabel()); } } - System.out.println("time1: " + (System.nanoTime() - t)); - long t2 = System.nanoTime(); List adjacentEdgesU = targetGraph.getEdges(u); // Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); @@ -277,8 +280,6 @@ private double calcLowerBoundMappingCost(Vertex v, multisetLabelsU.add(edge.getLabel()); } } - System.out.println("time2: " + (System.nanoTime() - t2)); - long t3 = System.nanoTime(); int anchoredVerticesCost = 0; @@ -293,13 +294,10 @@ private double calcLowerBoundMappingCost(Vertex v, anchoredVerticesCost++; } } - System.out.println("time3: " + (System.nanoTime() - t3)); - long t4 = System.nanoTime(); Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); // System.out.println("equalNodes : " + (equalNodes ? 0 : 1) + " editDistance " + (multiSetEditDistance / 2.0) + " anchored cost" + (anchoredVerticesCost)); - System.out.println("time4: " + (System.nanoTime() - t4)); return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 7b31ce8b88..2d774a27ad 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -83,8 +83,8 @@ public int size() { public void addIsolatedVertices(int count) { String uniqueType = String.valueOf(UUID.randomUUID()); for (int i = 0; i < count; i++) { - Vertex isolatedVertex = new Vertex(uniqueType); - vertices.add(isolatedVertex); + Vertex isolatedVertex = Vertex.newArtificialNode(uniqueType); + vertices.add( isolatedVertex); } } } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index d562d04b55..6574eb4aa4 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -8,14 +8,23 @@ public class Vertex { private String type; private Map properties = new LinkedHashMap<>(); private String debugName; + private boolean artificialNode; - public Vertex(String type) { - this.type = type; + public static Vertex newArtificialNode(String type) { + Vertex vertex = new Vertex(type, null); + vertex.artificialNode = true; + return vertex; } + public Vertex(String type, String debugName) { this.type = type; this.debugName = debugName; } + + public boolean isArtificialNode() { + return artificialNode; + } + public void add(String propName, Object propValue) { properties.put(propName, propValue); } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 080e4f4fb5..c783d0cdc5 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1,6 +1,7 @@ package graphql.schema.diffing import graphql.TestUtil +import graphql.language.OperationDefinition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchemaElement import graphql.schema.GraphQLTypeVisitorStub @@ -287,6 +288,244 @@ class SchemaDiffingTest extends Specification { } + def "changing schema a lot 1"() { + given: + def schema1 = schema(""" + type Query { + pets: [Pet] + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + pets: [Animal] @deprecated + animals: [Animal] + } + interface Animal { + name: String + friend: Human + } + type Human { + name: String + } + interface Pet implements Animal { + name: String + friend: Human + } + type Dog implements Pet & Animal { + name: String + friend: Human + } + type Cat implements Pet & Animal { + name: String + friend: Human + } + type Fish implements Pet & Animal { + name: String + friend: Human + } + """) + + when: + new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + true + + } + + def "adding a few things "() { + given: + def schema1 = schema(""" + type Query { + pets: [Pet] + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + pets: [Pet] + animals: [Animal] + } + interface Animal { + name: String + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + type Fish implements Pet{ + name: String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 20 + } + + def "adding a few things plus introducing new interface"() { + given: + def schema1 = schema(""" + type Query { + pets: [Pet] + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + pets: [Pet] + animals: [Animal] + } + interface Animal { + name: String + } + interface Pet implements Animal { + name: String + } + type Dog implements Pet & Animal { + name: String + } + type Cat implements Pet & Animal { + name: String + } + type Fish implements Pet & Animal { + name: String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 24 + } + + def "adding a few things plus introducing new interface plus changing return type"() { + given: + def schema1 = schema(""" + type Query { + pets: [Pet] + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + pets: [Animal] # This is different from the previous one + animals: [Animal] + } + interface Animal { + name: String + } + interface Pet implements Animal { + name: String + } + type Dog implements Pet & Animal { + name: String + } + type Cat implements Pet & Animal { + name: String + } + type Fish implements Pet & Animal { + name: String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 26 + } + + def "adding a few things plus introducing new interface plus changing return type plus adding field in Interface"() { + given: + def schema1 = schema(""" + type Query { + pets: [Pet] + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + type Cat implements Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + pets: [Pet] + } + interface Animal { + name: String + friend: String + } + interface Pet implements Animal { + name: String + friend: String + } + type Dog implements Pet & Animal { + name: String + friend: String + } + type Cat implements Pet & Animal { + name: String + friend: String + } + type Fish implements Pet & Animal { + name: String + friend: String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 48 + } + def "add a field"() { given: def schema1 = schema(""" From 6046ab6abe005a273cd69216fafc461a4992bee3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 9 Dec 2021 16:33:48 +1100 Subject: [PATCH 011/294] wip --- .../graphql/schema/diffing/SchemaDiffing.java | 4 +-- .../graphql/schema/diffing/SchemaGraph.java | 2 +- .../schema/diffing/SchemaDiffingTest.groovy | 26 +++++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index fa8b49a71b..9cb4349445 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -266,7 +266,7 @@ private double calcLowerBoundMappingCost(Vertex v, // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices // or: at least one edge is part of the partial mapping // if (nonMappedSourceVertices.contains(edge.getOne()) && nonMappedSourceVertices.contains(edge.getTwo())) { - if (!partialMappingSourceSet.contains(edge.getOne()) || !partialMappingSourceSet.contains(edge.getTwo())) { + if (!partialMappingSourceSet.contains(edge.getOne()) && !partialMappingSourceSet.contains(edge.getTwo())) { multisetLabelsV.add(edge.getLabel()); } } @@ -276,7 +276,7 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { // test if this is an inner edge - if (!partialMappingSourceSet.contains(edge.getOne()) || !partialMappingTargetSet.contains(edge.getTwo())) { + if (!partialMappingTargetSet.contains(edge.getOne()) && !partialMappingTargetSet.contains(edge.getTwo())) { multisetLabelsU.add(edge.getLabel()); } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 2d774a27ad..7899c0d188 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -84,7 +84,7 @@ public void addIsolatedVertices(int count) { String uniqueType = String.valueOf(UUID.randomUUID()); for (int i = 0; i < count; i++) { Vertex isolatedVertex = Vertex.newArtificialNode(uniqueType); - vertices.add( isolatedVertex); + vertices.add(isolatedVertex); } } } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index c783d0cdc5..927d889783 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -45,10 +45,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 1 } @@ -72,10 +72,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 4 } @@ -83,7 +83,7 @@ class SchemaDiffingTest extends Specification { given: def schema1 = schema(""" type Query { - hello: Int + hello: Boolean } """) def schema2 = schema(""" @@ -93,10 +93,14 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + /** + * Deleting the edge from __DUMMY_TYPE_VERTICE to Boolean + * Insert the edge from __DUMMY_TYPE_VERTICE to String + */ + diff.size() == 2 } @@ -120,10 +124,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 2 } @@ -450,7 +454,7 @@ class SchemaDiffingTest extends Specification { """) def schema2 = schema(""" type Query { - pets: [Animal] # This is different from the previous one + pets: [Animal] animals: [Animal] } interface Animal { @@ -474,7 +478,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 26 + operations.size() == 24 } def "adding a few things plus introducing new interface plus changing return type plus adding field in Interface"() { From 22e11482520bd276506718fc15fb4e4f62a5591a Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 9 Dec 2021 16:47:50 +1100 Subject: [PATCH 012/294] wip --- .../schema/diffing/SchemaDiffingTest.groovy | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 927d889783..037dde5935 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -24,10 +24,10 @@ class SchemaDiffingTest extends Specification { """) when: - def schemaGraph = new SchemaDiffing().createGraph(schema) + def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: - schemaGraph.size() == 64 + schemaGraph.size() == 132 } @@ -188,11 +188,9 @@ class SchemaDiffingTest extends Specification { }) println "changed fields: " + counter when: - long t = System.currentTimeMillis() - new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) - println "time: " + (System.currentTimeMillis() - t) + def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) then: - true + diff.size() == 171 } def "change object type name used twice"() { @@ -217,10 +215,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 3 } @@ -240,10 +238,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 1 } @@ -263,10 +261,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 2 } @@ -285,14 +283,13 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true - + diff.size() == 5 } - def "changing schema a lot 1"() { + def "changing schema a lot"() { given: def schema1 = schema(""" type Query { @@ -339,10 +336,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 58 } @@ -388,7 +385,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 20 + operations.size() == 18 } def "adding a few things plus introducing new interface"() { @@ -433,7 +430,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 24 + operations.size() == 22 } def "adding a few things plus introducing new interface plus changing return type"() { @@ -527,7 +524,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 48 + operations.size() == 42 } def "add a field"() { @@ -545,10 +542,10 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 5 } From 6963902c380fe2afc26c1a152e2c753195cee9e0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 9 Dec 2021 17:40:42 +1100 Subject: [PATCH 013/294] wip --- .../schema/diffing/SchemaDiffingTest.groovy | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 037dde5935..c8a56b02f6 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -165,10 +165,47 @@ class SchemaDiffingTest extends Specification { """) when: - new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - true + diff.size() == 1 + + } + + def "change Object into an Interface"() { + given: + def schema1 = schema(""" + type Query { + luna: Pet + } + type Pet { + name: String + } + """) + def schema2 = schema(""" + type Query { + luna: Pet + } + interface Pet { + name: String + } + type Dog implements Pet { + name: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + /** + * 1. Change type Pet into Interface Pet + * 2,3,4: Insert Object Dog, Insert Field name, Insert __DUMMY_TYPE_VERTICE + * 5. Insert Edge from Object Dog to Field name + * 6,7 Insert Edge from Field name to DUMMY_TYPE_VERTICE to Scalar String + * 8. Insert 'implements' Edge from Object Pet to Interface Pet + */ + diff.size() == 8 } From dad0fbda4ddcf669c92be14d0bff5596d42cd2a0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 9 Dec 2021 20:25:48 +1100 Subject: [PATCH 014/294] higher level diff analyzer --- .../DefaultGraphEditOperationAnalyzer.java | 96 +++++++++++++++++++ .../graphql/schema/diffing/EditOperation.java | 19 +++- .../schema/diffing/SchemaChangedHandler.java | 10 ++ .../graphql/schema/diffing/SchemaDiffing.java | 23 +++-- .../graphql/schema/diffing/SchemaGraph.java | 11 ++- .../java/graphql/schema/diffing/Vertex.java | 5 + .../schema/diffing/SchemaDiffingTest.groovy | 13 ++- 7 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java create mode 100644 src/main/java/graphql/schema/diffing/SchemaChangedHandler.java diff --git a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java new file mode 100644 index 0000000000..ad704e7e44 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java @@ -0,0 +1,96 @@ +package graphql.schema.diffing; + +import graphql.Assert; +import graphql.schema.GraphQLSchema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import static graphql.Assert.assertNotNull; + +/** + * Higher level GraphQL semantic assigned to + */ +public class DefaultGraphEditOperationAnalyzer { + + private GraphQLSchema oldSchema; + private GraphQLSchema newSchema; + private SchemaGraph oldSchemaGraph; + private SchemaGraph newSchemaGraph; + private SchemaChangedHandler schemaChangedHandler; + + public DefaultGraphEditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, SchemaGraph oldSchemaGraph, SchemaGraph newSchemaGraph, SchemaChangedHandler schemaChangedHandler) { + this.oldSchema = oldSchema; + this.newSchema = newSchema; + this.oldSchemaGraph = oldSchemaGraph; + this.newSchemaGraph = newSchemaGraph; + this.schemaChangedHandler = schemaChangedHandler; + } + + public void analyzeEdits(List editOperations) { + for (EditOperation editOperation : editOperations) { + // 5 edit operations + if (editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { + Vertex deletedVertex = editOperation.getDetails(); + if (!deletedVertex.getType().equals("Field")) { + continue; + } + String fieldName = deletedVertex.getProperty("name"); + // find the "dummy-type" vertex for this field + Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTICE))); + Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); + + Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> + edge.getOne().getType().equals("Object") || edge.getOne().getType().equals("Interface") || + edge.getTwo().getType().equals("Object") || edge.getTwo().getType().equals("Interface") + )); + Edge edgeFromDummyTypeToType = Assert.assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(dummyTypeVertex, edge -> edge != edgeToDummyTypeVertex)); + + List relatedEditOperations = searchForOperations(editOperations, Arrays.asList( + eo -> { + if (eo.getOperation() == EditOperation.Operation.DELETE_VERTEX) { + return eo.getDetails() == dummyTypeVertex; + } + return false; + }, + eo -> { + if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { + return eo.getDetails() == edgeToObjectOrInterface; + } + return false; + }, + eo -> { + if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { + return eo.getDetails() == edgeToDummyTypeVertex; + } + return false; + }, + eo -> { + if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { + return eo.getDetails() == edgeFromDummyTypeToType; + } + return false; + }) + ); + if (relatedEditOperations.size() == 4) { + schemaChangedHandler.fieldRemoved("Field " + edgeToObjectOrInterface.getOne().get("name") + "." + fieldName + " removed"); + } + } + } + } + + private List searchForOperations(List editOperations, List> predicates) { + List result = new ArrayList<>(); + for (EditOperation editOperation : editOperations) { + for (Predicate predicate : predicates) { + if (predicate.test(editOperation)) { + result.add(editOperation); + } + } + } + return result; + } + +} diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index b9ab05c30c..00b25eda58 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -1,23 +1,34 @@ package graphql.schema.diffing; public class EditOperation { - public EditOperation(Operation operation, String detail) { + + public EditOperation(Operation operation, String description, Object details) { this.operation = operation; - this.detail = detail; + this.description = description; + this.details = details; } private Operation operation; - private String detail; + private String description; + private Object details; enum Operation { CHANGE_VERTEX, DELETE_VERTEX, INSERT_VERTEX, CHANGE_EDGE, INSERT_EDGE, DELETE_EDGE } + public Operation getOperation() { + return operation; + } + + public T getDetails() { + return (T) details; + } + @Override public String toString() { return "EditOperation{" + "operation=" + operation + - ", detail='" + detail + '\'' + + ", description='" + description + '\'' + '}'; } } diff --git a/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java b/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java new file mode 100644 index 0000000000..2f1a7807bc --- /dev/null +++ b/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java @@ -0,0 +1,10 @@ +package graphql.schema.diffing; + +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLType; + +public interface SchemaChangedHandler { + + void fieldRemoved(String description); + +} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 9cb4349445..100be9602a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -32,9 +32,12 @@ public MappingEntry() { List availableSiblings = new ArrayList<>(); } + SchemaGraph sourceGraph; + SchemaGraph targetGraph; + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { - SchemaGraph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); - SchemaGraph targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); + sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); + targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); // System.out.println(GraphPrinter.print(sourceGraph)); return diffImpl(sourceGraph, targetGraph); } @@ -199,11 +202,11 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); if (!equalNodes) { if (sourceVertex.isArtificialNode()) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_VERTEX, "Insert" + targetVertex)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_VERTEX, "Insert" + targetVertex, targetVertex)); } else if (targetVertex.isArtificialNode()) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_VERTEX, "Delete " + sourceVertex)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_VERTEX, "Delete " + sourceVertex, sourceVertex)); } else { - editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex, Arrays.asList(sourceVertex, targetVertex))); } cost++; } @@ -220,10 +223,10 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so Vertex target2 = partialOrFullMapping.getTarget(sourceEdge.getTwo()); Edge targetEdge = targetGraph.getEdge(target1, target2); if (targetEdge == null) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_EDGE, "Delete edge " + sourceEdge)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_EDGE, "Delete edge " + sourceEdge, sourceEdge)); cost++; } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_EDGE, "Change " + sourceEdge + " to " + targetEdge)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_EDGE, "Change " + sourceEdge + " to " + targetEdge, Arrays.asList(sourceEdge, targetEdge))); cost++; } } @@ -237,7 +240,7 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so Vertex sourceFrom = partialOrFullMapping.getSource(targetEdge.getOne()); Vertex sourceTo = partialOrFullMapping.getSource(targetEdge.getTwo()); if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_EDGE, "Insert edge " + targetEdge)); + editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_EDGE, "Insert edge " + targetEdge, targetEdge)); cost++; } } @@ -258,7 +261,7 @@ private double calcLowerBoundMappingCost(Vertex v, // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges - List adjacentEdgesV = sourceGraph.getEdges(v); + List adjacentEdgesV = sourceGraph.getAdjacentEdges(v); // Set nonMappedSourceVertices = nonMappedVertices(sourceGraph.getVertices(), partialMappingSourceList); Multiset multisetLabelsV = HashMultiset.create(); @@ -271,7 +274,7 @@ private double calcLowerBoundMappingCost(Vertex v, } } - List adjacentEdgesU = targetGraph.getEdges(u); + List adjacentEdgesU = targetGraph.getAdjacentEdges(u); // Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 7899c0d188..5811c5c6ed 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -39,10 +39,19 @@ public void addEdge(Edge edge) { edgeByVertexPair.put(edge.getTwo(), edge.getOne(), edge); } - public List getEdges(Vertex from) { + public List getAdjacentEdges(Vertex from) { return new ArrayList<>(edgeByVertexPair.row(from).values()); } + public Edge getSingleAdjacentEdge(Vertex from, Predicate predicate) { + for (Edge edge : edgeByVertexPair.row(from).values()) { + if(predicate.test(edge)) { + return edge; + } + } + return null; + } + public List getEdges() { return edges; } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 6574eb4aa4..a5279fe174 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -10,6 +10,7 @@ public class Vertex { private String debugName; private boolean artificialNode; + public static Vertex newArtificialNode(String type) { Vertex vertex = new Vertex(type, null); vertex.artificialNode = true; @@ -37,6 +38,10 @@ public T get(String propName) { return (T) properties.get(propName); } + public T getProperty(String name) { + return (T) properties.get(name); + } + public Map getProperties() { return properties; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index c8a56b02f6..71bfcc0df6 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -52,6 +52,7 @@ class SchemaDiffingTest extends Specification { } + def "test two field renames one type rename"() { given: def schema1 = schema(""" @@ -319,13 +320,23 @@ class SchemaDiffingTest extends Specification { } """) + def changeHandler = new SchemaChangedHandler() { + @Override + void fieldRemoved(String description) { + println "field removed: " + description + } + } + + def diffing = new SchemaDiffing() when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = diffing.diffGraphQLSchema(schema1, schema2) + new DefaultGraphEditOperationAnalyzer(schema1, schema2, diffing.sourceGraph, diffing.targetGraph, changeHandler).analyzeEdits(diff) then: diff.size() == 5 } + def "changing schema a lot"() { given: def schema1 = schema(""" From 36ee1d8ba026a21686fffcb440e45dfe6d800744 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 10 Dec 2021 13:46:28 +1100 Subject: [PATCH 015/294] performance --- .../graphql/schema/diffing/SchemaDiffing.java | 20 +++++++++---------- .../graphql/schema/diffing/SchemaGraph.java | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 100be9602a..e5896b7809 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -14,7 +14,7 @@ public class SchemaDiffing { private static class MappingEntry { - public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, List availableSiblings) { + public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, Set availableSiblings) { this.partialMapping = partialMapping; this.level = level; this.lowerBoundCost = lowerBoundCost; @@ -29,7 +29,7 @@ public MappingEntry() { Mapping partialMapping = new Mapping(); int level; double lowerBoundCost; - List availableSiblings = new ArrayList<>(); + Set availableSiblings = new LinkedHashSet<>(); } SchemaGraph sourceGraph; @@ -83,23 +83,23 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // generate children if (mappingEntry.level < graphSize) { // candidates are the vertices in target, of which are not used yet in partialMapping - List childCandidates = new ArrayList<>(targetGraph.getVertices()); + Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); } } - System.out.println("ged cost: " + upperBoundCost.doubleValue()); - System.out.println("edit : " + bestEdit); - for (EditOperation editOperation : bestEdit.get()) { - System.out.println(editOperation); - } +// System.out.println("ged cost: " + upperBoundCost.doubleValue()); +// System.out.println("edit : " + bestEdit); +// for (EditOperation editOperation : bestEdit.get()) { +// System.out.println(editOperation); +// } return bestEdit.get(); } // level starts at 1 indicating the level in the search tree to look for the next mapping private void genNextMapping(Mapping partialMapping, int level, - List candidates, + Set candidates, // changed in place on purpose PriorityQueue queue, AtomicDouble upperBound, AtomicReference bestMapping, @@ -211,7 +211,7 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so cost++; } } - List subGraphSource = sourceGraph.getVertices().subList(0, partialOrFullMapping.size()); + Set subGraphSource = new LinkedHashSet<>(sourceGraph.getVertices().subList(0, partialOrFullMapping.size())); List edges = sourceGraph.getEdges(); // edge deletion or relabeling for (Edge sourceEdge : edges) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 5811c5c6ed..34ddeaeeb9 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -45,9 +45,9 @@ public List getAdjacentEdges(Vertex from) { public Edge getSingleAdjacentEdge(Vertex from, Predicate predicate) { for (Edge edge : edgeByVertexPair.row(from).values()) { - if(predicate.test(edge)) { - return edge; - } + if (predicate.test(edge)) { + return edge; + } } return null; } @@ -93,7 +93,7 @@ public void addIsolatedVertices(int count) { String uniqueType = String.valueOf(UUID.randomUUID()); for (int i = 0; i < count; i++) { Vertex isolatedVertex = Vertex.newArtificialNode(uniqueType); - vertices.add(isolatedVertex); + vertices.add(0, isolatedVertex); } } } From 7c662fa89aed55553ae05ed1a21f877276d7e035 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 10 Dec 2021 13:50:00 +1100 Subject: [PATCH 016/294] performance --- src/main/java/graphql/schema/diffing/SchemaGraph.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 34ddeaeeb9..4f2e3f9ecc 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -93,7 +93,7 @@ public void addIsolatedVertices(int count) { String uniqueType = String.valueOf(UUID.randomUUID()); for (int i = 0; i < count; i++) { Vertex isolatedVertex = Vertex.newArtificialNode(uniqueType); - vertices.add(0, isolatedVertex); + vertices.add(isolatedVertex); } } } From 00d08dae994b7f6f27efb724baba06f0d0749e10 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 10 Dec 2021 15:34:15 +1100 Subject: [PATCH 017/294] sorting work --- .../java/graphql/schema/diffing/Edge.java | 6 ++ .../graphql/schema/diffing/SchemaDiffing.java | 88 +++++++++++++++++++ .../graphql/schema/diffing/SchemaGraph.java | 5 ++ .../java/graphql/schema/diffing/Vertex.java | 7 ++ 4 files changed, 106 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index 7aeea521ed..9c134e8d8e 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import java.util.Objects; + public class Edge { private Vertex one; private Vertex two; @@ -50,4 +52,8 @@ public String toString() { ", label='" + label + '\'' + '}'; } + + public boolean isEqualTo(Edge other) { + return Objects.equals(this.label, other.label); + } } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index e5896b7809..fcec19cbf6 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -52,6 +52,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); + sortSourceGraph(sourceGraph, targetGraph); AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); @@ -96,6 +97,93 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { return bestEdit.get(); } + private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Map vertexWeights = new LinkedHashMap<>(); + Map edgesWeights = new LinkedHashMap<>(); + for (Vertex vertex : sourceGraph.getVertices()) { + vertexWeights.put(vertex, infrequencyWeightForVertex(vertex, targetGraph)); + } + for (Edge edge : sourceGraph.getEdges()) { + edgesWeights.put(edge, infrequencyWeightForEdge(edge, targetGraph)); + } + // start with the vertex with largest total weight + List result = new ArrayList<>(); + ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); + nextCandidates.sort(Comparator.comparingInt(o -> totalWeight(sourceGraph, o, vertexWeights, edgesWeights))); +// System.out.println("0: " + totalWeight(sourceGraph, nextCandidates.get(0), vertexWeights, edgesWeights)); +// System.out.println("last: " + totalWeight(sourceGraph, nextCandidates.get(nextCandidates.size() - 1), vertexWeights, edgesWeights)); +// // starting with the one with largest totalWeight: + Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); + result.add(curVertex); + nextCandidates.remove(nextCandidates.size() - 1); + + while (nextCandidates.size() > 0) { + Vertex nextOne = null; + int curMaxWeight = Integer.MIN_VALUE; + int index = 0; + int nextOneIndex = -1; + for (Vertex candidate : nextCandidates) { + int totalWeight = totalWeight(sourceGraph, candidate, allAdjacentEdges(sourceGraph, result, candidate), vertexWeights, edgesWeights); + if (totalWeight > curMaxWeight) { + nextOne = candidate; + nextOneIndex = index; + curMaxWeight = totalWeight; + } + index++; + } + result.add(nextOne); + nextCandidates.remove(nextOneIndex); + } +// System.out.println(result); +// System.out.println(nextCandidates); +// Collections.reverse(result); + sourceGraph.setVertices(result); + } + + private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { + List result = new ArrayList<>(); + for (Vertex from : fromList) { + Edge edge = schemaGraph.getEdge(from, to); + if (edge == null) { + continue; + } + result.add(edge); + } + return result; + } + + private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, List edges, Map vertexWeights, Map edgesWeights) { +// if (vertex.isArtificialNode()) { +// return Integer.MIN_VALUE + 1; +// } + return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); + } + + private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, Map vertexWeights, Map edgesWeights) { + List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); + return vertexWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesWeights::get).sum(); + } + + private int infrequencyWeightForVertex(Vertex sourceVertex, SchemaGraph targetGraph) { + int count = 0; + for (Vertex targetVertex : targetGraph.getVertices()) { + if (sourceVertex.isEqualTo(targetVertex)) { + count++; + } + } + return 1 - count; + } + + private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { + int count = 0; + for (Edge targetEdge : targetGraph.getEdges()) { + if (sourceEdge.isEqualTo(targetEdge)) { + count++; + } + } + return 1 - count; + } + // level starts at 1 indicating the level in the search tree to look for the next mapping private void genNextMapping(Mapping partialMapping, int level, diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 4f2e3f9ecc..e7c9aa4402 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -56,6 +56,7 @@ public List getEdges() { return edges; } + // null if the edge doesn't exist public @Nullable Edge getEdge(Vertex from, Vertex to) { return edgeByVertexPair.get(from, to); } @@ -65,6 +66,10 @@ public List getVertices() { return vertices; } + public void setVertices(List vertices) { + this.vertices = vertices; + } + public void addType(String name, Vertex vertex) { typesByName.put(name, vertex); } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index a5279fe174..e69af6256a 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -2,6 +2,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; public class Vertex { @@ -50,6 +51,12 @@ public String getDebugName() { return debugName; } + public boolean isEqualTo(Vertex other) { + return other != null && + Objects.equals(this.type, other.type) && + Objects.equals(this.properties, other.properties); + } + @Override public String toString() { return "Vertex{" + From 8be4509f917c887f0cd23f20d6a817fd84edda0e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 11 Dec 2021 09:32:02 +1100 Subject: [PATCH 018/294] sorting work --- .../graphql/schema/diffing/SchemaDiffing.java | 17 +++-- .../schema/diffing/SchemaGraphFactory.java | 73 ++++++++++++++----- .../java/graphql/schema/diffing/Vertex.java | 11 +++ .../schema/diffing/SchemaDiffingTest.groovy | 49 +++++++++++++ 4 files changed, 123 insertions(+), 27 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index fcec19cbf6..c03cd9c772 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -70,7 +70,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); - System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " queue size: " + queue.size()); + System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost); if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { // System.out.println("skipping!"); continue; @@ -134,10 +134,10 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { result.add(nextOne); nextCandidates.remove(nextOneIndex); } -// System.out.println(result); + System.out.println(result); // System.out.println(nextCandidates); // Collections.reverse(result); - sourceGraph.setVertices(result); +// sourceGraph.setVertices(result); } private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { @@ -153,9 +153,12 @@ private List allAdjacentEdges(SchemaGraph schemaGraph, List fromLi } private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, List edges, Map vertexWeights, Map edgesWeights) { -// if (vertex.isArtificialNode()) { -// return Integer.MIN_VALUE + 1; -// } + if (vertex.isBuiltInType()) { + return Integer.MIN_VALUE + 1; + } + if (vertex.isArtificialNode()) { + return Integer.MIN_VALUE + 2; + } return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); } @@ -245,7 +248,7 @@ private void genNextMapping(Mapping partialMapping, Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); candidates.remove(bestExtensionTargetVertex); -// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); +// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); // we have a full mapping from the cost matrix diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 830d98162e..9f349e5fea 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -1,6 +1,10 @@ package graphql.schema.diffing; +import graphql.Directives; +import graphql.introspection.Introspection; import graphql.schema.*; +import graphql.schema.idl.DirectiveInfo; +import graphql.schema.idl.ScalarInfo; import graphql.util.TraversalControl; import graphql.util.Traverser; import graphql.util.TraverserContext; @@ -33,26 +37,38 @@ public SchemaGraph createGraph(GraphQLSchema schema) { roots.add(schema.getIntrospectionSchemaType()); Traverser traverser = Traverser.depthFirst(GraphQLSchemaElement::getChildren); SchemaGraph schemaGraph = new SchemaGraph(); + class IntrospectionNode { + + } traverser.traverse(roots, new TraverserVisitor() { @Override public TraversalControl enter(TraverserContext context) { + boolean isIntrospectionNode = false; + if (context.thisNode() instanceof GraphQLNamedType) { + if (Introspection.isIntrospectionTypes((GraphQLNamedType) context.thisNode())) { + isIntrospectionNode = true; + context.setVar(IntrospectionNode.class, new IntrospectionNode()); + } + } else { + isIntrospectionNode = context.getVarFromParents(IntrospectionNode.class) != null; + } if (context.thisNode() instanceof GraphQLObjectType) { - newObject((GraphQLObjectType) context.thisNode(), schemaGraph); + newObject((GraphQLObjectType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLInterfaceType) { - newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph); + newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLUnionType) { - newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph); + newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLScalarType) { - newScalar((GraphQLScalarType) context.thisNode(), schemaGraph); + newScalar((GraphQLScalarType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLInputObjectType) { - newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph); + newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLEnumType) { - newEnum((GraphQLEnumType) context.thisNode(), schemaGraph); + newEnum((GraphQLEnumType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLDirective) { // only continue if not applied directive @@ -110,6 +126,7 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); + dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); @@ -165,6 +182,7 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); + dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); @@ -188,12 +206,13 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume schemaGraph.addEdge(typeEdge); } - private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph) { + private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex objectVertex = new Vertex("Object", String.valueOf(counter++)); + objectVertex.setBuiltInType(isIntrospectionNode); objectVertex.add("name", graphQLObjectType.getName()); objectVertex.add("description", graphQLObjectType.getDescription()); for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { - Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(newFieldVertex); schemaGraph.addEdge(new Edge(objectVertex, newFieldVertex)); } @@ -202,12 +221,13 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr cratedAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); } - private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph) { + private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex fieldVertex = new Vertex("Field", String.valueOf(counter++)); + fieldVertex.setBuiltInType(isIntrospectionNode); fieldVertex.add("name", graphQLFieldDefinition.getName()); fieldVertex.add("description", graphQLFieldDefinition.getDescription()); for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { - Vertex argumentVertex = newArgument(argument, schemaGraph); + Vertex argumentVertex = newArgument(argument, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(argumentVertex); schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex)); } @@ -215,16 +235,21 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra return fieldVertex; } - private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { + private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex vertex = new Vertex("Argument", String.valueOf(counter++)); + vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); vertex.add("description", graphQLArgument.getDescription()); cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); return vertex; } - private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { + private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex scalarVertex = new Vertex("Scalar", String.valueOf(counter++)); + scalarVertex.setBuiltInType(isIntrospectionNode); + if (ScalarInfo.isGraphqlSpecifiedScalar(scalarType.getName())) { + scalarVertex.setBuiltInType(true); + } scalarVertex.add("name", scalarType.getName()); scalarVertex.add("description", scalarType.getDescription()); schemaGraph.addVertex(scalarVertex); @@ -232,12 +257,13 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph) { cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); } - private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph) { + private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex interfaceVertex = new Vertex("Interface", String.valueOf(counter++)); + interfaceVertex.setBuiltInType(isIntrospectionNode); interfaceVertex.add("name", interfaceType.getName()); interfaceVertex.add("description", interfaceType.getDescription()); for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { - Vertex newFieldVertex = newField(fieldDefinition, schemaGraph); + Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(newFieldVertex); schemaGraph.addEdge(new Edge(interfaceVertex, newFieldVertex)); } @@ -246,12 +272,14 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema cratedAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); } - private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { + private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex enumVertex = new Vertex("Enum", String.valueOf(counter++)); + enumVertex.setBuiltInType(isIntrospectionNode); enumVertex.add("name", enumType.getName()); enumVertex.add("description", enumType.getDescription()); for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { Vertex enumValueVertex = new Vertex("EnumValue", String.valueOf(counter++)); + enumValueVertex.setBuiltInType(isIntrospectionNode); enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); @@ -262,8 +290,9 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph) { cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); } - private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { + private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex unionVertex = new Vertex("Union", String.valueOf(counter++)); + unionVertex.setBuiltInType(isIntrospectionNode); unionVertex.add("name", unionType.getName()); unionVertex.add("description", unionType.getDescription()); schemaGraph.addVertex(unionVertex); @@ -271,12 +300,13 @@ private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph) { cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); } - private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph) { + private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex inputObjectVertex = new Vertex("InputObject", String.valueOf(counter++)); + inputObjectVertex.setBuiltInType(isIntrospectionNode); inputObjectVertex.add("name", inputObject.getName()); inputObjectVertex.add("description", inputObject.getDescription()); for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { - Vertex newInputField = newInputField(inputObjectField, schemaGraph); + Vertex newInputField = newInputField(inputObjectField, schemaGraph, isIntrospectionNode); Edge newEdge = new Edge(inputObjectVertex, newInputField); schemaGraph.addEdge(newEdge); cratedAppliedDirectives(inputObjectVertex, inputObjectField.getDirectives(), schemaGraph); @@ -305,9 +335,11 @@ private void cratedAppliedDirectives(Vertex from, List applied private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { Vertex directiveVertex = new Vertex("Directive", String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); + boolean graphqlSpecified = DirectiveInfo.isGraphqlSpecifiedDirective(directive.getName()); + directiveVertex.setBuiltInType(graphqlSpecified); directiveVertex.add("description", directive.getDescription()); for (GraphQLArgument argument : directive.getArguments()) { - Vertex argumentVertex = newArgument(argument, schemaGraph); + Vertex argumentVertex = newArgument(argument, schemaGraph, graphqlSpecified); schemaGraph.addVertex(argumentVertex); schemaGraph.addEdge(new Edge(directiveVertex, argumentVertex)); } @@ -315,8 +347,9 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { schemaGraph.addVertex(directiveVertex); } - private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph) { + private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex vertex = new Vertex("InputField", String.valueOf(counter++)); + vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); vertex.add("description", inputField.getDescription()); cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index e69af6256a..7b36ca0a27 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -12,6 +12,8 @@ public class Vertex { private boolean artificialNode; + private boolean builtInType; + public static Vertex newArtificialNode(String type) { Vertex vertex = new Vertex(type, null); vertex.artificialNode = true; @@ -57,12 +59,21 @@ public boolean isEqualTo(Vertex other) { Objects.equals(this.properties, other.properties); } + public boolean isBuiltInType() { + return builtInType; + } + + public void setBuiltInType(boolean builtInType) { + this.builtInType = builtInType; + } + @Override public String toString() { return "Vertex{" + "type='" + type + '\'' + ", properties=" + properties + ", debugName='" + debugName + '\'' + + ", builtInType='" + builtInType + '\'' + '}'; } } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 71bfcc0df6..d9e3807a7c 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -597,6 +597,55 @@ class SchemaDiffingTest extends Specification { } + def "add a field and Type"() { + given: + def schema1 = schema(""" + type Query { + hello: String + } + """) + def schema2 = schema(""" + type Query { + hello: String + newField: Foo + } + type Foo { + foo: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + diff.size() == 11 + + } + + def "add a field and Type and remove a field"() { + given: + def schema1 = schema(""" + type Query { + hello: String + } + """) + def schema2 = schema(""" + type Query { + newField: Foo + } + type Foo { + foo: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + diff.size() == 8 + + } + def "test example schema"() { given: def source = buildSourceGraph() From 570a67e1b8e8d7bb1f97000a9c86592b4032e1a6 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 15 Dec 2021 12:04:09 +1100 Subject: [PATCH 019/294] generate all children at once --- .../schema/diffing/HungarianAlgorithm.java | 82 ++++- .../java/graphql/schema/diffing/Mapping.java | 3 +- .../graphql/schema/diffing/SchemaDiffing.java | 321 +++++++++++++++--- .../schema/diffing/SchemaGraphFactory.java | 12 +- .../schema/diffing/SchemaDiffingTest.groovy | 181 +++++----- 5 files changed, 454 insertions(+), 145 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index d86307ca2a..01e2702224 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -48,15 +48,31 @@ * @author Kevin L. Stern */ public class HungarianAlgorithm { + // changed by reduce private final double[][] costMatrix; - private final int rows, cols, dim; - private final double[] labelByWorker, labelByJob; + + // constant always + private final int rows; + private final int cols; + private final int dim; + + // the assigned workers,jobs for the result + private final int[] matchJobByWorker; + private final int[] matchWorkerByJob; + + // reset for each execute private final int[] minSlackWorkerByJob; private final double[] minSlackValueByJob; - private final int[] matchJobByWorker, matchWorkerByJob; private final int[] parentWorkerByCommittedJob; + // reset for worker private final boolean[] committedWorkers; + + // labels for both sides of the bipartite graph + private final double[] labelByWorker; + private final double[] labelByJob; + + /** * Construct an instance of the algorithm. * @@ -131,9 +147,9 @@ public int[] execute() { * smallest element, compute an initial non-zero dual feasible solution and * create a greedy matching from workers to jobs of the cost matrix. */ - reduce(); +// reduce(); computeInitialFeasibleSolution(); - greedyMatch(); +// greedyMatch(); int w = fetchUnmatchedWorker(); while (w < dim) { @@ -170,7 +186,9 @@ public int[] execute() { */ protected void executePhase() { while (true) { - int minSlackWorker = -1, minSlackJob = -1; + // the last worker we found + int minSlackWorker = -1; + int minSlackJob = -1; double minSlackValue = Double.POSITIVE_INFINITY; for (int j = 0; j < dim; j++) { if (parentWorkerByCommittedJob[j] == -1) { @@ -184,10 +202,17 @@ protected void executePhase() { if (minSlackValue > 0) { updateLabeling(minSlackValue); } + // adding (minSlackWorker, minSlackJob) to the path parentWorkerByCommittedJob[minSlackJob] = minSlackWorker; + // check if minSlackJob is not assigned yet + // the requirement of an augmenting path is that start and end is not matched (assigned) yet if (matchWorkerByJob[minSlackJob] == -1) { /* * An augmenting path has been found. + * Iterating via parentWorkerByCommittedJob and matchJobByWorker through the + * path and add/update matching. + * Job -> Parent worker via parentWorkerByCommittedJob + * and Worker -> Job via matchJobByWorker */ int committedJob = minSlackJob; int parentWorker = parentWorkerByCommittedJob[committedJob]; @@ -206,7 +231,9 @@ protected void executePhase() { * Update slack values since we increased the size of the committed * workers set. */ + // we checked above that minSlackJob is indeed assigned int worker = matchWorkerByJob[minSlackJob]; + // committedWorkers is used when slack is updated committedWorkers[worker] = true; for (int j = 0; j < dim; j++) { if (parentWorkerByCommittedJob[j] == -1) { @@ -262,8 +289,7 @@ protected void initializePhase(int w) { Arrays.fill(parentWorkerByCommittedJob, -1); committedWorkers[w] = true; for (int j = 0; j < dim; j++) { - minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] - - labelByJob[j]; + minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] - labelByJob[j]; minSlackWorkerByJob[j] = w; } } @@ -331,4 +357,44 @@ protected void updateLabeling(double slack) { } } } + + public int[] nextChild(int jobIndex, int[] prevResult) { + int currentJobAssigned = matchJobByWorker[0]; + // we want to make currentJobAssigned not allowed,meaning we set the size to Infinity + double oldCost = costMatrix[0][currentJobAssigned]; + costMatrix[0][currentJobAssigned] = Integer.MAX_VALUE; + matchWorkerByJob[currentJobAssigned] = -1; + matchJobByWorker[0] = -1; + +// for (int w = 0; w < dim; w++) { +// labelByWorker[w] = 0; +// } +// for (int j = 0; j < dim; j++) { +// labelByJob[j] -= 0; +// } +// computeInitialFeasibleSolution(); +// System.out.println("worker label: " + labelByWorker[0]); +// System.out.println("job label: " + labelByJob[currentJobAssigned]); +// System.out.println("added: " + (labelByWorker[0] + labelByJob[currentJobAssigned]) + " vs old cost " + oldCost); + minSlackValueByJob[currentJobAssigned] = Integer.MAX_VALUE; + initializePhase(0); + executePhase(); + int[] result = Arrays.copyOf(matchJobByWorker, rows); + return result; +// } else { +// // this means we are good with previous result because the jobIndex was not picked anyway +// return prevResult; +// } + } + + public static void main(String[] args) { + double[][] c = new double[][]{{1, 33, 4}, {3, 5, 2}, {2, 4, 4}}; + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(c); + int[] result = hungarianAlgorithm.execute(); + System.out.println(Arrays.toString(result)); + for (int i = 0; i < c.length - 1; i++) { + result = hungarianAlgorithm.nextChild(i, result); + System.out.println(Arrays.toString(result)); + } + } } \ No newline at end of file diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java index e22cabfd38..fc54eaae52 100644 --- a/src/main/java/graphql/schema/diffing/Mapping.java +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -2,17 +2,16 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import graphql.Assert; import java.util.ArrayList; import java.util.List; -import java.util.Set; public class Mapping { private BiMap map = HashBiMap.create(); private List sourceList = new ArrayList<>(); private List targetList = new ArrayList<>(); + int[] targetIndices; private Mapping(BiMap map, List sourceList, List targetList) { this.map = map; this.sourceList = sourceList; diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index c03cd9c772..5b7288babf 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -6,19 +6,27 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import static graphql.Assert.assertNotNull; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; +import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; +import static graphql.schema.diffing.SchemaGraphFactory.UNION; public class SchemaDiffing { private static class MappingEntry { - public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, Set availableSiblings) { + public List mappingEntriesSiblings = new ArrayList<>(); + public int[] assignments; + public ArrayList availableTargetVertices; + + public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, Set candidates) { this.partialMapping = partialMapping; this.level = level; this.lowerBoundCost = lowerBoundCost; - this.availableSiblings = availableSiblings; + this.candidates = candidates; } public MappingEntry() { @@ -29,20 +37,25 @@ public MappingEntry() { Mapping partialMapping = new Mapping(); int level; double lowerBoundCost; - Set availableSiblings = new LinkedHashSet<>(); + Set candidates = new LinkedHashSet<>(); } SchemaGraph sourceGraph; SchemaGraph targetGraph; - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) { sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); // System.out.println(GraphPrinter.print(sourceGraph)); - return diffImpl(sourceGraph, targetGraph); + return diffImpl(sourceGraph, targetGraph, oldVersion); + + } + + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph, boolean oldVersion) { // we assert here that the graphs have the same size. The algorithm depends on it if (sourceGraph.size() < targetGraph.size()) { sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); @@ -58,7 +71,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { AtomicReference bestFullMapping = new AtomicReference<>(); AtomicReference> bestEdit = new AtomicReference<>(); - PriorityQueue queue = new PriorityQueue((mappingEntry1, mappingEntry2) -> { + PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); if (compareResult == 0) { return (-1) * Integer.compare(mappingEntry1.level, mappingEntry2.level); @@ -68,35 +81,83 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { }); queue.add(new MappingEntry()); int counter = 0; - while (!queue.isEmpty()) { - MappingEntry mappingEntry = queue.poll(); - System.out.println("entry at level " + mappingEntry.level + " counter:" + (++counter) + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost); - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + if (oldVersion) { + + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); + System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { // System.out.println("skipping!"); - continue; - } - // generate sibling - if (mappingEntry.level > 0 && mappingEntry.availableSiblings.size() > 0) { - // we need to remove the last mapping - Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); - genNextMapping(parentMapping, mappingEntry.level, mappingEntry.availableSiblings, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + continue; + } + // generate sibling + if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { + // we need to remove the last mapping + Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); + System.out.println("generate sibling"); + genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + } + // generate children + if (mappingEntry.level < graphSize) { + // candidates are the vertices in target, of which are not used yet in partialMapping + Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); + childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); + System.out.println("generate child"); + genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + } } - // generate children - if (mappingEntry.level < graphSize) { - // candidates are the vertices in target, of which are not used yet in partialMapping - Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); - genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + } else { + + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); + System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { +// System.out.println("skipping!"); + continue; + } + // generate sibling + if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { + System.out.println("get sibling"); + getSibling( + mappingEntry.level, + mappingEntry.candidates, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + sourceGraph, + targetGraph, + mappingEntry); + } + // generate children + if (mappingEntry.level < graphSize) { + // candidates are the vertices in target, of which are not used yet in partialMapping + Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); + childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); + System.out.println("generate children"); + generateChilds(mappingEntry.partialMapping, + mappingEntry.level + 1, + childCandidates, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + sourceGraph, + targetGraph, + mappingEntry + ); + } } } -// System.out.println("ged cost: " + upperBoundCost.doubleValue()); -// System.out.println("edit : " + bestEdit); -// for (EditOperation editOperation : bestEdit.get()) { -// System.out.println(editOperation); -// } + System.out.println("ged cost: " + upperBoundCost.doubleValue()); + System.out.println("edit : " + bestEdit); + for (EditOperation editOperation : bestEdit.get()) { + System.out.println(editOperation); + } return bestEdit.get(); } + private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { Map vertexWeights = new LinkedHashMap<>(); Map edgesWeights = new LinkedHashMap<>(); @@ -109,7 +170,7 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // start with the vertex with largest total weight List result = new ArrayList<>(); ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); - nextCandidates.sort(Comparator.comparingInt(o -> totalWeight(sourceGraph, o, vertexWeights, edgesWeights))); + nextCandidates.sort(Comparator.comparingInt(o -> totalWeightWithAdjacentEdges(sourceGraph, o, vertexWeights, edgesWeights))); // System.out.println("0: " + totalWeight(sourceGraph, nextCandidates.get(0), vertexWeights, edgesWeights)); // System.out.println("last: " + totalWeight(sourceGraph, nextCandidates.get(nextCandidates.size() - 1), vertexWeights, edgesWeights)); // // starting with the one with largest totalWeight: @@ -123,7 +184,7 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int index = 0; int nextOneIndex = -1; for (Vertex candidate : nextCandidates) { - int totalWeight = totalWeight(sourceGraph, candidate, allAdjacentEdges(sourceGraph, result, candidate), vertexWeights, edgesWeights); + int totalWeight = totalWeightWithSomeEdges(sourceGraph, candidate, allAdjacentEdges(sourceGraph, result, candidate), vertexWeights, edgesWeights); if (totalWeight > curMaxWeight) { nextOne = candidate; nextOneIndex = index; @@ -136,8 +197,7 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { } System.out.println(result); // System.out.println(nextCandidates); -// Collections.reverse(result); -// sourceGraph.setVertices(result); + sourceGraph.setVertices(result); } private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { @@ -152,7 +212,7 @@ private List allAdjacentEdges(SchemaGraph schemaGraph, List fromLi return result; } - private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, List edges, Map vertexWeights, Map edgesWeights) { + private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex vertex, List edges, Map vertexWeights, Map edgesWeights) { if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } @@ -162,7 +222,13 @@ private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, List edges return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); } - private int totalWeight(SchemaGraph sourceGraph, Vertex vertex, Map vertexWeights, Map edgesWeights) { + private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex vertex, Map vertexWeights, Map edgesWeights) { + if (vertex.isBuiltInType()) { + return Integer.MIN_VALUE + 1; + } + if (vertex.isArtificialNode()) { + return Integer.MIN_VALUE + 2; + } List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); return vertexWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesWeights::get).sum(); } @@ -187,7 +253,6 @@ private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { return 1 - count; } - // level starts at 1 indicating the level in the search tree to look for the next mapping private void genNextMapping(Mapping partialMapping, int level, Set candidates, // changed in place on purpose @@ -224,13 +289,10 @@ private void genNextMapping(Mapping partialMapping, } else { double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); costMatrix[i - level + 1][j] = cost; -// System.out.println("lower bound cost for mapping " + v + " to " + u + " is " + cost + " with index " + (i - level + 1) + " => " + j); -// System.out.println("cost counter: " + (costCounter++) + "/" + overallCount + " percentage: " + (costCounter / (float) overallCount)); } j++; } } -// System.out.println("finished matrix"); // find out the best extension HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int[] assignments = hungarianAlgorithm.execute(); @@ -248,7 +310,7 @@ private void genNextMapping(Mapping partialMapping, Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); candidates.remove(bestExtensionTargetVertex); -// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); + System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); // we have a full mapping from the cost matrix @@ -263,7 +325,118 @@ private void genNextMapping(Mapping partialMapping, upperBound.set(costForFullMapping); bestMapping.set(fullMapping); bestEdit.set(editOperations); - System.out.println("setting new best edit with size " + editOperations.size() + " at level " + level); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + } else { +// System.out.println("to expensive cost for overall mapping " +); + } + } else { + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); +// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); + } + } + + + // level starts at 1 indicating the level in the search tree to look for the next mapping + private void generateChilds(Mapping partialMapping, + int level, + Set candidates, // changed in place on purpose + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference bestMapping, + AtomicReference> bestEdit, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + MappingEntry parentOrSiblingEntry + ) { + assertTrue(level - 1 == partialMapping.size()); + List sourceList = sourceGraph.getVertices(); + List targetList = targetGraph.getVertices(); + + ArrayList availableTargetVertices = new ArrayList<>(targetList); + availableTargetVertices.removeAll(partialMapping.getTargets()); + assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); + // level starts at 1 ... therefore level - 1 is the current one we want to extend + Vertex v_i = sourceList.get(level - 1); + + + int costMatrixSize = sourceList.size() - level + 1; + double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + + + // we are skipping the first level -i indices + Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); + Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); + + // costMatrix[0] is the row for v_i + for (int i = level - 1; i < sourceList.size(); i++) { + Vertex v = sourceList.get(i); + int j = 0; + for (Vertex u : availableTargetVertices) { +// if (v == v_i && !candidates.contains(u)) { +// costMatrix[i - level + 1][j] = Integer.MAX_VALUE; +// } else { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); + costMatrix[i - level + 1][j] = cost; +// } + j++; + } + } + // find out the best extension + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); + int[] assignments = hungarianAlgorithm.execute(); + + + // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i + int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); + + double costMatrixSum = getCostMatrixSum(costMatrix, assignments); + double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; + + if (lowerBoundForPartialMapping < upperBound.doubleValue()) { + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); + + // generate all siblings + List siblings = new ArrayList<>(); + for (int child = 1; child < availableTargetVertices.size(); child++) { + int[] siblingAssignment = hungarianAlgorithm.nextChild(child, assignments); + double costMatrixSumSibling = getCostMatrixSum(costMatrix, siblingAssignment); + double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; + // this must be always something else + int v_i_target_IndexSibling = siblingAssignment[0]; + +// System.out.println("job index: " + v_i_target_IndexSibling); + Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); + Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); +// candidates.remove(bestExtensionTargetVertex); + MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling, candidates); + sibling.mappingEntriesSiblings = siblings; + sibling.assignments = siblingAssignment; + sibling.availableTargetVertices = availableTargetVertices; + siblings.add( sibling); + } + + MappingEntry e = new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates); + e.mappingEntriesSiblings = siblings; + System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); + queue.add(e); + + // we have a full mapping from the cost matrix + Mapping fullMapping = partialMapping.copy(); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + } + assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } else { // System.out.println("to expensive cost for overall mapping " +); } @@ -275,6 +448,72 @@ private void genNextMapping(Mapping partialMapping, } } + private void getSibling( + int level, + Set __noUseded, + PriorityQueue queue, + AtomicDouble upperBoundCost, + AtomicReference bestFullMapping, + AtomicReference> bestEdit, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + MappingEntry mappingEntry) { + + MappingEntry sibling = mappingEntry.mappingEntriesSiblings.get(0); + if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { + System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); + + queue.add(sibling); + mappingEntry.mappingEntriesSiblings.remove(0); + + List sourceList = sourceGraph.getVertices(); + List targetList = targetGraph.getVertices(); + + // we expect here the parent mapping, this is why we remove the last element + Mapping fullMapping = sibling.partialMapping.removeLastElement(); + for (int i = 0; i < sibling.assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), sibling.availableTargetVertices.get(sibling.assignments[i])); + } + assertTrue(fullMapping.size() == this.sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, this.sourceGraph, this.targetGraph, editOperations); + if (costForFullMapping < upperBoundCost.doubleValue()) { + upperBoundCost.set(costForFullMapping); + bestFullMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + + } + } + } + + + private double getCostMatrixSum(double[][] costMatrix, int[] assignments) { + double costMatrixSum = 0; + for (int i = 0; i < assignments.length; i++) { + costMatrixSum += costMatrix[i][assignments[i]]; + } + return costMatrixSum; + } + + private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + String viType = v_i.getType(); + HashMultiset viAdjacentEdges = HashMultiset.create(sourceGraph.getAdjacentEdges(v_i).stream().map(edge -> edge.getLabel()).collect(Collectors.toList())); + if (viType.equals(SchemaGraphFactory.OBJECT) || viType.equals(INTERFACE) || viType.equals(UNION) || viType.equals(INPUT_OBJECT)) { + for (Vertex targetVertex : availableTargetVertices) { + if (v_i.isEqualTo(targetVertex)) { + // check if edges are the same + HashMultiset adjacentEdges = HashMultiset.create(targetGraph.getAdjacentEdges(targetVertex).stream().map(Edge::getLabel).collect(Collectors.toList())); + if (viAdjacentEdges.equals(adjacentEdges)) { + return targetVertex; + } + } + + } + } + return null; + } + private List getDebugMap(Mapping mapping) { List result = new ArrayList<>(); for (Map.Entry entry : mapping.getMap().entrySet()) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 9f349e5fea..9beea4cc43 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -20,6 +20,10 @@ public class SchemaGraphFactory { public static final String DUMMY_TYPE_VERTICE = "__DUMMY_TYPE_VERTICE"; + public static final String OBJECT = "Object"; + public static final String INTERFACE = "Interface"; + public static final String UNION = "Union"; + public static final String INPUT_OBJECT = "InputObject"; private int counter = 1; public SchemaGraph createGraph(GraphQLSchema schema) { @@ -87,16 +91,16 @@ public TraversalControl leave(TraverserContext context) { ArrayList copyOfVertices = new ArrayList<>(schemaGraph.getVertices()); for (Vertex vertex : copyOfVertices) { - if ("Object".equals(vertex.getType())) { + if (OBJECT.equals(vertex.getType())) { handleObjectVertex(vertex, schemaGraph, schema); } - if ("Interface".equals(vertex.getType())) { + if (INTERFACE.equals(vertex.getType())) { handleInterfaceVertex(vertex, schemaGraph, schema); } - if ("Union".equals(vertex.getType())) { + if (UNION.equals(vertex.getType())) { handleUnion(vertex, schemaGraph, schema); } - if ("InputObject".equals(vertex.getType())) { + if (INPUT_OBJECT.equals(vertex.getType())) { handleInputObject(vertex, schemaGraph, schema); } if ("AppliedDirective".equals(vertex.getType())) { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index d9e3807a7c..af6b0d1486 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1,13 +1,13 @@ package graphql.schema.diffing import graphql.TestUtil -import graphql.language.OperationDefinition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchemaElement import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer import graphql.util.TraversalControl import graphql.util.TraverserContext +import spock.lang.Ignore import spock.lang.Specification import static graphql.TestUtil.schema @@ -46,7 +46,7 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) - + println diff then: diff.size() == 1 @@ -94,7 +94,7 @@ class SchemaDiffingTest extends Specification { """) when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) then: /** @@ -210,6 +210,7 @@ class SchemaDiffingTest extends Specification { } + @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) @@ -384,7 +385,7 @@ class SchemaDiffingTest extends Specification { """) when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2,false) then: diff.size() == 58 @@ -646,90 +647,90 @@ class SchemaDiffingTest extends Specification { } - def "test example schema"() { - given: - def source = buildSourceGraph() - def target = buildTargetGraph() - when: - new SchemaDiffing().diffImpl(source, target) - then: - true - } - - def "test example schema 2"() { - given: - def source = sourceGraph2() - def target = targetGraph2() - when: - new SchemaDiffing().diffImpl(source, target) - then: - true - } - - SchemaGraph sourceGraph2() { - def source = new SchemaGraph() - Vertex a = new Vertex("A") - source.addVertex(a) - Vertex b = new Vertex("B") - source.addVertex(b) - Vertex c = new Vertex("C") - source.addVertex(c) - Vertex d = new Vertex("D") - source.addVertex(d) - source.addEdge(new Edge(a, b)) - source.addEdge(new Edge(b, c)) - source.addEdge(new Edge(c, d)) - source - } - - SchemaGraph targetGraph2() { - def target = new SchemaGraph() - Vertex a = new Vertex("A") - Vertex d = new Vertex("D") - target.addVertex(a) - target.addVertex(d) - target - } - - SchemaGraph buildTargetGraph() { - SchemaGraph targetGraph = new SchemaGraph(); - def a_1 = new Vertex("A", "u1") - def d = new Vertex("D", "u2") - def a_2 = new Vertex("A", "u3") - def a_3 = new Vertex("A", "u4") - def e = new Vertex("E", "u5") - targetGraph.addVertex(a_1); - targetGraph.addVertex(d); - targetGraph.addVertex(a_2); - targetGraph.addVertex(a_3); - targetGraph.addVertex(e); - - targetGraph.addEdge(new Edge(a_1, d, "a")) - targetGraph.addEdge(new Edge(d, a_2, "a")) - targetGraph.addEdge(new Edge(a_2, a_3, "a")) - targetGraph.addEdge(new Edge(a_3, e, "a")) - targetGraph - - } - - SchemaGraph buildSourceGraph() { - SchemaGraph sourceGraph = new SchemaGraph(); - def c = new Vertex("C", "v5") - def a_1 = new Vertex("A", "v1") - def a_2 = new Vertex("A", "v2") - def a_3 = new Vertex("A", "v3") - def b = new Vertex("B", "v4") - sourceGraph.addVertex(a_1); - sourceGraph.addVertex(a_2); - sourceGraph.addVertex(a_3); - sourceGraph.addVertex(b); - sourceGraph.addVertex(c); - - sourceGraph.addEdge(new Edge(c, a_1, "b")) - sourceGraph.addEdge(new Edge(a_1, a_2, "a")) - sourceGraph.addEdge(new Edge(a_2, a_3, "a")) - sourceGraph.addEdge(new Edge(a_3, b, "a")) - sourceGraph - - } +// def "test example schema"() { +// given: +// def source = buildSourceGraph() +// def target = buildTargetGraph() +// when: +// new SchemaDiffing().diffImpl(source, target, oldVersion) +// then: +// true +// } +// +// def "test example schema 2"() { +// given: +// def source = sourceGraph2() +// def target = targetGraph2() +// when: +// new SchemaDiffing().diffImpl(source, target, oldVersion) +// then: +// true +// } +// +// SchemaGraph sourceGraph2() { +// def source = new SchemaGraph() +// Vertex a = new Vertex("A") +// source.addVertex(a) +// Vertex b = new Vertex("B") +// source.addVertex(b) +// Vertex c = new Vertex("C") +// source.addVertex(c) +// Vertex d = new Vertex("D") +// source.addVertex(d) +// source.addEdge(new Edge(a, b)) +// source.addEdge(new Edge(b, c)) +// source.addEdge(new Edge(c, d)) +// source +// } +// +// SchemaGraph targetGraph2() { +// def target = new SchemaGraph() +// Vertex a = new Vertex("A") +// Vertex d = new Vertex("D") +// target.addVertex(a) +// target.addVertex(d) +// target +// } +// +// SchemaGraph buildTargetGraph() { +// SchemaGraph targetGraph = new SchemaGraph(); +// def a_1 = new Vertex("A", "u1") +// def d = new Vertex("D", "u2") +// def a_2 = new Vertex("A", "u3") +// def a_3 = new Vertex("A", "u4") +// def e = new Vertex("E", "u5") +// targetGraph.addVertex(a_1); +// targetGraph.addVertex(d); +// targetGraph.addVertex(a_2); +// targetGraph.addVertex(a_3); +// targetGraph.addVertex(e); +// +// targetGraph.addEdge(new Edge(a_1, d, "a")) +// targetGraph.addEdge(new Edge(d, a_2, "a")) +// targetGraph.addEdge(new Edge(a_2, a_3, "a")) +// targetGraph.addEdge(new Edge(a_3, e, "a")) +// targetGraph +// +// } +// +// SchemaGraph buildSourceGraph() { +// SchemaGraph sourceGraph = new SchemaGraph(); +// def c = new Vertex("C", "v5") +// def a_1 = new Vertex("A", "v1") +// def a_2 = new Vertex("A", "v2") +// def a_3 = new Vertex("A", "v3") +// def b = new Vertex("B", "v4") +// sourceGraph.addVertex(a_1); +// sourceGraph.addVertex(a_2); +// sourceGraph.addVertex(a_3); +// sourceGraph.addVertex(b); +// sourceGraph.addVertex(c); +// +// sourceGraph.addEdge(new Edge(c, a_1, "b")) +// sourceGraph.addEdge(new Edge(a_1, a_2, "a")) +// sourceGraph.addEdge(new Edge(a_2, a_3, "a")) +// sourceGraph.addEdge(new Edge(a_3, b, "a")) +// sourceGraph +// +// } } From 400a63517c8eaac070f789f0f7978b15286045d9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 15 Dec 2021 12:28:44 +1100 Subject: [PATCH 020/294] cleanup ab it --- .../schema/diffing/HungarianAlgorithm.java | 4 +- src/main/java/graphql/schema/diffing/Old.java | 120 ++++++++++ .../graphql/schema/diffing/SchemaDiffing.java | 221 ++++-------------- .../graphql/schema/diff/SchemaDiffTest.groovy | 52 ++--- 4 files changed, 193 insertions(+), 204 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/Old.java diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index 01e2702224..ce1eac96ad 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -147,9 +147,9 @@ public int[] execute() { * smallest element, compute an initial non-zero dual feasible solution and * create a greedy matching from workers to jobs of the cost matrix. */ -// reduce(); + reduce(); computeInitialFeasibleSolution(); -// greedyMatch(); + greedyMatch(); int w = fetchUnmatchedWorker(); while (w < dim) { diff --git a/src/main/java/graphql/schema/diffing/Old.java b/src/main/java/graphql/schema/diffing/Old.java new file mode 100644 index 0000000000..2647f19404 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/Old.java @@ -0,0 +1,120 @@ +//package graphql.schema.diffing; +// +//public class Old { +//} + +/* + if (oldVersion) { + + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); + System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { +// System.out.println("skipping!"); + continue; + } + // generate sibling + if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { + // we need to remove the last mapping + Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); + System.out.println("generate sibling"); + genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + } + // generate children + if (mappingEntry.level < graphSize) { + // candidates are the vertices in target, of which are not used yet in partialMapping + Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); + childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); + System.out.println("generate child"); + genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); + } + } + } else { + + + private void genNextMapping(Mapping partialMapping, + int level, + Set candidates, // changed in place on purpose + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference bestMapping, + AtomicReference> bestEdit, + SchemaGraph sourceGraph, + SchemaGraph targetGraph) { + assertTrue(level - 1 == partialMapping.size()); + List sourceList = sourceGraph.getVertices(); + List targetList = targetGraph.getVertices(); + ArrayList availableTargetVertices = new ArrayList<>(targetList); + availableTargetVertices.removeAll(partialMapping.getTargets()); + assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); + // level starts at 1 ... therefore level - 1 is the current one we want to extend + Vertex v_i = sourceList.get(level - 1); + int costMatrixSize = sourceList.size() - level + 1; + double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + + + // we are skipping the first level -i indeces + int costCounter = 0; + int overallCount = (sourceList.size() - level) * availableTargetVertices.size(); + Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); + Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); + + for (int i = level - 1; i < sourceList.size(); i++) { + Vertex v = sourceList.get(i); + int j = 0; + for (Vertex u : availableTargetVertices) { + if (v == v_i && !candidates.contains(u)) { + costMatrix[i - level + 1][j] = Integer.MAX_VALUE; + } else { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); + costMatrix[i - level + 1][j] = cost; + } + j++; + } + } + // find out the best extension + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); + int[] assignments = hungarianAlgorithm.execute(); + + // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i + int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); + double costMatrixSum = 0; + for (int i = 0; i < assignments.length; i++) { + costMatrixSum += costMatrix[i][assignments[i]]; + } + double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; + + if (lowerBoundForPartialMapping < upperBound.doubleValue()) { + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); + candidates.remove(bestExtensionTargetVertex); +// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); + queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); + + // we have a full mapping from the cost matrix + Mapping fullMapping = partialMapping.copy(); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + } + assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + } else { +// System.out.println("to expensive cost for overall mapping " +); + } + } else { + int v_i_target_Index = assignments[0]; + Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); + Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); +// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); + } + } + + +*/ \ No newline at end of file diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 5b7288babf..c6e677fc41 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -22,11 +22,10 @@ private static class MappingEntry { public int[] assignments; public ArrayList availableTargetVertices; - public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost, Set candidates) { + public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost) { this.partialMapping = partialMapping; this.level = level; this.lowerBoundCost = lowerBoundCost; - this.candidates = candidates; } public MappingEntry() { @@ -37,7 +36,7 @@ public MappingEntry() { Mapping partialMapping = new Mapping(); int level; double lowerBoundCost; - Set candidates = new LinkedHashSet<>(); +// Set candidates = new LinkedHashSet<>(); } SchemaGraph sourceGraph; @@ -47,7 +46,7 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); // System.out.println(GraphPrinter.print(sourceGraph)); - return diffImpl(sourceGraph, targetGraph, oldVersion); + return diffImpl(sourceGraph, targetGraph); } @@ -55,7 +54,7 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph, boolean oldVersion) { + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { // we assert here that the graphs have the same size. The algorithm depends on it if (sourceGraph.size() < targetGraph.size()) { sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); @@ -81,72 +80,44 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph, b }); queue.add(new MappingEntry()); int counter = 0; - if (oldVersion) { - while (!queue.isEmpty()) { - MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); + System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { // System.out.println("skipping!"); - continue; - } - // generate sibling - if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { - // we need to remove the last mapping - Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); - System.out.println("generate sibling"); - genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); - } - // generate children - if (mappingEntry.level < graphSize) { - // candidates are the vertices in target, of which are not used yet in partialMapping - Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); - System.out.println("generate child"); - genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); - } + continue; } - } else { - - while (!queue.isEmpty()) { - MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { -// System.out.println("skipping!"); - continue; - } - // generate sibling - if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { - System.out.println("get sibling"); - getSibling( - mappingEntry.level, - mappingEntry.candidates, - queue, - upperBoundCost, - bestFullMapping, - bestEdit, - sourceGraph, - targetGraph, - mappingEntry); - } - // generate children - if (mappingEntry.level < graphSize) { - // candidates are the vertices in target, of which are not used yet in partialMapping - Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); - System.out.println("generate children"); - generateChilds(mappingEntry.partialMapping, - mappingEntry.level + 1, - childCandidates, - queue, - upperBoundCost, - bestFullMapping, - bestEdit, - sourceGraph, - targetGraph, - mappingEntry - ); - } + // generate sibling + if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { +// System.out.println("get sibling"); + getSibling( + mappingEntry.level, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + sourceGraph, + targetGraph, + mappingEntry); + } + // generate children + if (mappingEntry.level < graphSize) { + // candidates are the vertices in target, of which are not used yet in partialMapping + Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); + childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); +// System.out.println("generate children"); + generateChilds(mappingEntry.partialMapping, + mappingEntry.level + 1, + childCandidates, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + sourceGraph, + targetGraph, + mappingEntry + ); } } System.out.println("ged cost: " + upperBoundCost.doubleValue()); @@ -253,95 +224,11 @@ private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { return 1 - count; } - private void genNextMapping(Mapping partialMapping, - int level, - Set candidates, // changed in place on purpose - PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestMapping, - AtomicReference> bestEdit, - SchemaGraph sourceGraph, - SchemaGraph targetGraph) { - assertTrue(level - 1 == partialMapping.size()); - List sourceList = sourceGraph.getVertices(); - List targetList = targetGraph.getVertices(); - ArrayList availableTargetVertices = new ArrayList<>(targetList); - availableTargetVertices.removeAll(partialMapping.getTargets()); - assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); - // level starts at 1 ... therefore level - 1 is the current one we want to extend - Vertex v_i = sourceList.get(level - 1); - int costMatrixSize = sourceList.size() - level + 1; - double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - - - // we are skipping the first level -i indeces - int costCounter = 0; - int overallCount = (sourceList.size() - level) * availableTargetVertices.size(); - Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); - Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); - - for (int i = level - 1; i < sourceList.size(); i++) { - Vertex v = sourceList.get(i); - int j = 0; - for (Vertex u : availableTargetVertices) { - if (v == v_i && !candidates.contains(u)) { - costMatrix[i - level + 1][j] = Integer.MAX_VALUE; - } else { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); - costMatrix[i - level + 1][j] = cost; - } - j++; - } - } - // find out the best extension - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); - int[] assignments = hungarianAlgorithm.execute(); - - // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i - int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - double costMatrixSum = 0; - for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i][assignments[i]]; - } - double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; - - if (lowerBoundForPartialMapping < upperBound.doubleValue()) { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); - candidates.remove(bestExtensionTargetVertex); - System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); - queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); - - // we have a full mapping from the cost matrix - Mapping fullMapping = partialMapping.copy(); - for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); - } - assertTrue(fullMapping.size() == sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } else { -// System.out.println("to expensive cost for overall mapping " +); - } - } else { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); -// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); - } - } - // level starts at 1 indicating the level in the search tree to look for the next mapping private void generateChilds(Mapping partialMapping, int level, - Set candidates, // changed in place on purpose + Set candiates, // changed in place on purpose PriorityQueue queue, AtomicDouble upperBound, AtomicReference bestMapping, @@ -412,16 +299,16 @@ private void generateChilds(Mapping partialMapping, Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); // candidates.remove(bestExtensionTargetVertex); - MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling, candidates); + MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); sibling.mappingEntriesSiblings = siblings; sibling.assignments = siblingAssignment; sibling.availableTargetVertices = availableTargetVertices; - siblings.add( sibling); + siblings.add(sibling); } - MappingEntry e = new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates); + MappingEntry e = new MappingEntry(newMapping, level, lowerBoundForPartialMapping); e.mappingEntriesSiblings = siblings; - System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); +// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); queue.add(e); // we have a full mapping from the cost matrix @@ -450,7 +337,6 @@ private void generateChilds(Mapping partialMapping, private void getSibling( int level, - Set __noUseded, PriorityQueue queue, AtomicDouble upperBoundCost, AtomicReference bestFullMapping, @@ -461,7 +347,7 @@ private void getSibling( MappingEntry sibling = mappingEntry.mappingEntriesSiblings.get(0); if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { - System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); +// System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); mappingEntry.mappingEntriesSiblings.remove(0); @@ -634,21 +520,4 @@ private double calcLowerBoundMappingCost(Vertex v, return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } - private Set nonMappedVertices(List allVertices, List partialMapping) { - Set set = new LinkedHashSet<>(allVertices); - partialMapping.forEach(set::remove); - return set; - } - - - private ImmutableList removeVertex(ImmutableList list, Vertex toRemove) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (Vertex vertex : list) { - if (vertex != toRemove) { - builder.add(vertex); - } - } - return builder.build(); - } - } diff --git a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy index 25b2dd5e95..e1ff7c5124 100644 --- a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy +++ b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy @@ -62,31 +62,31 @@ class SchemaDiffTest extends Specification { return RuntimeWiring.newRuntimeWiring() .wiringFactory(new WiringFactory() { - @Override - boolean providesTypeResolver(UnionWiringEnvironment environment) { - return true - } - - @Override - boolean providesTypeResolver(InterfaceWiringEnvironment environment) { - return true - } - - @Override - TypeResolver getTypeResolver(InterfaceWiringEnvironment environment) { - return NULL_TYPE_RESOLVER - } - - @Override - TypeResolver getTypeResolver(UnionWiringEnvironment environment) { - return NULL_TYPE_RESOLVER - } - - @Override - DataFetcher getDefaultDataFetcher(FieldWiringEnvironment environment) { - return new PropertyDataFetcher(environment.getFieldDefinition().getName()) - } - }) + @Override + boolean providesTypeResolver(UnionWiringEnvironment environment) { + return true + } + + @Override + boolean providesTypeResolver(InterfaceWiringEnvironment environment) { + return true + } + + @Override + TypeResolver getTypeResolver(InterfaceWiringEnvironment environment) { + return NULL_TYPE_RESOLVER + } + + @Override + TypeResolver getTypeResolver(UnionWiringEnvironment environment) { + return NULL_TYPE_RESOLVER + } + + @Override + DataFetcher getDefaultDataFetcher(FieldWiringEnvironment environment) { + return new PropertyDataFetcher(environment.getFieldDefinition().getName()) + } + }) .scalar(CUSTOM_SCALAR) .build() } @@ -238,7 +238,7 @@ class SchemaDiffTest extends Specification { reporter.breakageCount == 0 List newFieldEvents = reporter.infos.stream() - .filter{de -> de.typeName == "Ainur" && de.fieldName == "surname"} + .filter { de -> de.typeName == "Ainur" && de.fieldName == "surname" } .collect(Collectors.toList()) newFieldEvents.size() == 2 From 89575a732f110d7979ac877eb94e1f8ac8276c66 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 15 Dec 2021 16:11:17 +1100 Subject: [PATCH 021/294] cleanup --- .../schema/diffing/HungarianAlgorithm.java | 20 +----- .../java/graphql/schema/diffing/Mapping.java | 1 - .../graphql/schema/diffing/SchemaDiffing.java | 68 ++++++++++--------- 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index ce1eac96ad..cb6327bb90 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -358,33 +358,17 @@ protected void updateLabeling(double slack) { } } - public int[] nextChild(int jobIndex, int[] prevResult) { + public int[] nextChild() { int currentJobAssigned = matchJobByWorker[0]; // we want to make currentJobAssigned not allowed,meaning we set the size to Infinity - double oldCost = costMatrix[0][currentJobAssigned]; costMatrix[0][currentJobAssigned] = Integer.MAX_VALUE; matchWorkerByJob[currentJobAssigned] = -1; matchJobByWorker[0] = -1; - -// for (int w = 0; w < dim; w++) { -// labelByWorker[w] = 0; -// } -// for (int j = 0; j < dim; j++) { -// labelByJob[j] -= 0; -// } -// computeInitialFeasibleSolution(); -// System.out.println("worker label: " + labelByWorker[0]); -// System.out.println("job label: " + labelByJob[currentJobAssigned]); -// System.out.println("added: " + (labelByWorker[0] + labelByJob[currentJobAssigned]) + " vs old cost " + oldCost); minSlackValueByJob[currentJobAssigned] = Integer.MAX_VALUE; initializePhase(0); executePhase(); int[] result = Arrays.copyOf(matchJobByWorker, rows); return result; -// } else { -// // this means we are good with previous result because the jobIndex was not picked anyway -// return prevResult; -// } } public static void main(String[] args) { @@ -393,7 +377,7 @@ public static void main(String[] args) { int[] result = hungarianAlgorithm.execute(); System.out.println(Arrays.toString(result)); for (int i = 0; i < c.length - 1; i++) { - result = hungarianAlgorithm.nextChild(i, result); + result = hungarianAlgorithm.nextChild(); System.out.println(Arrays.toString(result)); } } diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java index fc54eaae52..196a992546 100644 --- a/src/main/java/graphql/schema/diffing/Mapping.java +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -11,7 +11,6 @@ public class Mapping { private List sourceList = new ArrayList<>(); private List targetList = new ArrayList<>(); - int[] targetIndices; private Mapping(BiMap map, List sourceList, List targetList) { this.map = map; this.sourceList = sourceList; diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index c6e677fc41..1b347bcf6e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -83,14 +83,15 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); +// System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if ((++counter) % 100 == 0) { + System.out.println((counter) + " entry at level"); + } if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { // System.out.println("skipping!"); continue; } - // generate sibling if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { -// System.out.println("get sibling"); getSibling( mappingEntry.level, queue, @@ -101,22 +102,15 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { targetGraph, mappingEntry); } - // generate children if (mappingEntry.level < graphSize) { - // candidates are the vertices in target, of which are not used yet in partialMapping - Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); -// System.out.println("generate children"); - generateChilds(mappingEntry.partialMapping, + generateChildren(mappingEntry, mappingEntry.level + 1, - childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, - targetGraph, - mappingEntry + targetGraph ); } } @@ -226,21 +220,21 @@ private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { // level starts at 1 indicating the level in the search tree to look for the next mapping - private void generateChilds(Mapping partialMapping, - int level, - Set candiates, // changed in place on purpose - PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestMapping, - AtomicReference> bestEdit, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - MappingEntry parentOrSiblingEntry + private void generateChildren(MappingEntry parentEntry, + int level, + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference bestMapping, + AtomicReference> bestEdit, + SchemaGraph sourceGraph, + SchemaGraph targetGraph ) { + Mapping partialMapping = parentEntry.partialMapping; assertTrue(level - 1 == partialMapping.size()); List sourceList = sourceGraph.getVertices(); List targetList = targetGraph.getVertices(); + // TODO: iterates over all target vertices ArrayList availableTargetVertices = new ArrayList<>(targetList); availableTargetVertices.removeAll(partialMapping.getTargets()); assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); @@ -286,19 +280,32 @@ private void generateChilds(Mapping partialMapping, Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); + if (lowerBoundForPartialMapping == parentEntry.lowerBoundCost) { +// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertex); + } + // generate all siblings List siblings = new ArrayList<>(); for (int child = 1; child < availableTargetVertices.size(); child++) { - int[] siblingAssignment = hungarianAlgorithm.nextChild(child, assignments); + int[] siblingAssignment = hungarianAlgorithm.nextChild(); double costMatrixSumSibling = getCostMatrixSum(costMatrix, siblingAssignment); double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; + + if (lowerBoundForPartialMappingSibling == parentEntry.lowerBoundCost) { +// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertex); + } + + if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { + break; + } + + +// if(lowerBoundForPartialMappingSibling == ) // this must be always something else int v_i_target_IndexSibling = siblingAssignment[0]; -// System.out.println("job index: " + v_i_target_IndexSibling); Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); -// candidates.remove(bestExtensionTargetVertex); MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); sibling.mappingEntriesSiblings = siblings; sibling.assignments = siblingAssignment; @@ -353,9 +360,7 @@ private void getSibling( mappingEntry.mappingEntriesSiblings.remove(0); List sourceList = sourceGraph.getVertices(); - List targetList = targetGraph.getVertices(); - - // we expect here the parent mapping, this is why we remove the last element + // we need to start here from the parent mapping, this is why we remove the last element Mapping fullMapping = sibling.partialMapping.removeLastElement(); for (int i = 0; i < sibling.assignments.length; i++) { fullMapping.add(sourceList.get(level - 1 + i), sibling.availableTargetVertices.get(sibling.assignments[i])); @@ -427,12 +432,11 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so cost++; } } - Set subGraphSource = new LinkedHashSet<>(sourceGraph.getVertices().subList(0, partialOrFullMapping.size())); List edges = sourceGraph.getEdges(); // edge deletion or relabeling for (Edge sourceEdge : edges) { // only edges relevant to the subgraph - if (!subGraphSource.contains(sourceEdge.getOne()) || !subGraphSource.contains(sourceEdge.getTwo())) { + if (!partialOrFullMapping.containsSource(sourceEdge.getOne()) || !partialOrFullMapping.containsSource(sourceEdge.getTwo())) { continue; } Vertex target1 = partialOrFullMapping.getTarget(sourceEdge.getOne()); @@ -447,7 +451,7 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so } } - // edge insertion + //TODO: iterates over all edges in the target Graph for (Edge targetEdge : targetGraph.getEdges()) { // only subgraph edges if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { @@ -478,7 +482,6 @@ private double calcLowerBoundMappingCost(Vertex v, // which are adjacent of u (resp. v) which are inner edges List adjacentEdgesV = sourceGraph.getAdjacentEdges(v); -// Set nonMappedSourceVertices = nonMappedVertices(sourceGraph.getVertices(), partialMappingSourceList); Multiset multisetLabelsV = HashMultiset.create(); for (Edge edge : adjacentEdgesV) { @@ -491,7 +494,6 @@ private double calcLowerBoundMappingCost(Vertex v, } List adjacentEdgesU = targetGraph.getAdjacentEdges(u); -// Set nonMappedTargetVertices = nonMappedVertices(targetGraph.getVertices(), partialMappingTargetList); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { // test if this is an inner edge From 5d817fa1d3f01a1d2bada31775f0ec4b7715fc66 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 15 Dec 2021 17:15:45 +1100 Subject: [PATCH 022/294] simplify --- .../graphql/schema/diffing/SchemaDiffing.java | 106 ++++++------------ 1 file changed, 37 insertions(+), 69 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1b347bcf6e..2d57f6b230 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -255,91 +255,59 @@ private void generateChildren(MappingEntry parentEntry, Vertex v = sourceList.get(i); int j = 0; for (Vertex u : availableTargetVertices) { -// if (v == v_i && !candidates.contains(u)) { -// costMatrix[i - level + 1][j] = Integer.MAX_VALUE; -// } else { double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); costMatrix[i - level + 1][j] = cost; -// } j++; } } - // find out the best extension HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); - int[] assignments = hungarianAlgorithm.execute(); - - - // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - double costMatrixSum = getCostMatrixSum(costMatrix, assignments); - double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; - - if (lowerBoundForPartialMapping < upperBound.doubleValue()) { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); - - if (lowerBoundForPartialMapping == parentEntry.lowerBoundCost) { -// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertex); + // generate all childrens (which are siblings to each other) + List siblings = new ArrayList<>(); + for (int child = 0; child < availableTargetVertices.size(); child++) { + int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); + double costMatrixSumSibling = getCostMatrixSum(costMatrix, assignments); + double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; +// System.out.println("lower bound: " + child + " : " + lowerBoundForPartialMappingSibling); + int v_i_target_IndexSibling = assignments[0]; + Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); + Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); + + if (lowerBoundForPartialMappingSibling == parentEntry.lowerBoundCost) { +// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertexSibling); } - // generate all siblings - List siblings = new ArrayList<>(); - for (int child = 1; child < availableTargetVertices.size(); child++) { - int[] siblingAssignment = hungarianAlgorithm.nextChild(); - double costMatrixSumSibling = getCostMatrixSum(costMatrix, siblingAssignment); - double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; - - if (lowerBoundForPartialMappingSibling == parentEntry.lowerBoundCost) { -// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertex); + if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { + break; + } + MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); + sibling.mappingEntriesSiblings = siblings; + sibling.assignments = assignments; + sibling.availableTargetVertices = availableTargetVertices; + + // first child we add to the queue, otherwise save it for later + if (child == 0) { + queue.add(sibling); + Mapping fullMapping = partialMapping.copy(); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); } - - if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { - break; + assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } - - -// if(lowerBoundForPartialMappingSibling == ) - // this must be always something else - int v_i_target_IndexSibling = siblingAssignment[0]; - - Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); - Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); - sibling.mappingEntriesSiblings = siblings; - sibling.assignments = siblingAssignment; - sibling.availableTargetVertices = availableTargetVertices; + } else { siblings.add(sibling); } - MappingEntry e = new MappingEntry(newMapping, level, lowerBoundForPartialMapping); - e.mappingEntriesSiblings = siblings; -// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); - queue.add(e); - - // we have a full mapping from the cost matrix - Mapping fullMapping = partialMapping.copy(); - for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); - } - assertTrue(fullMapping.size() == sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } else { -// System.out.println("to expensive cost for overall mapping " +); - } - } else { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); -// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); } + } private void getSibling( From aa007e4932cd6fb4fb68291d1a073ba2b7b08a89 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 16 Dec 2021 06:23:22 +1100 Subject: [PATCH 023/294] testing --- .../schema/diffing/SchemaGraphFactory.java | 5 +- .../schema/diffing/SchemaDiffingTest.groovy | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 9beea4cc43..80084e69e0 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -1,6 +1,5 @@ package graphql.schema.diffing; -import graphql.Directives; import graphql.introspection.Introspection; import graphql.schema.*; import graphql.schema.idl.DirectiveInfo; @@ -63,7 +62,7 @@ public TraversalControl enter(TraverserContext context) { newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLUnionType) { - newUnion((GraphQLInterfaceType) context.thisNode(), schemaGraph, isIntrospectionNode); + newUnion((GraphQLUnionType) context.thisNode(), schemaGraph, isIntrospectionNode); } if (context.thisNode() instanceof GraphQLScalarType) { newScalar((GraphQLScalarType) context.thisNode(), schemaGraph, isIntrospectionNode); @@ -294,7 +293,7 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); } - private void newUnion(GraphQLInterfaceType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { + private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex unionVertex = new Vertex("Union", String.valueOf(counter++)); unionVertex.setBuiltInType(isIntrospectionNode); unionVertex.add("name", unionType.getName()); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index af6b0d1486..0365227301 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -647,6 +647,52 @@ class SchemaDiffingTest extends Specification { } + + def "change a Union "() { + given: + def schema1 = schema(""" + type Query { + pet: Pet + } + union Pet = Dog | Cat + type Dog { + name: String + } + type Cat { + name: String + } + """) + def schema2 = schema(""" + type Query { + pet: Pet + } + union Pet = Dog | Bird | Fish + type Dog { + name: String + } + type Bird { + name: String + } + type Fish { + name: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + /** + * 1. Change Cat to Bird + * 2,3,4: Insert Fish, Insert Fish.name, Insert __DummyTypeVertice + * 5. Insert Edge from Fish to Fish.name + * 6.7. Insert Edge from Fish.name -> __DummyType --> String + * 8. Insert edge from Pet -> Fish + */ + diff.size() == 8 + + } + // def "test example schema"() { // given: // def source = buildSourceGraph() From b0cc0d987c7c516854b52a5d76b87568262a599b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 17 Dec 2021 16:33:45 +1100 Subject: [PATCH 024/294] optimizing --- .../DefaultGraphEditOperationAnalyzer.java | 2 +- .../schema/diffing/HungarianAlgorithm.java | 15 +--- .../java/graphql/schema/diffing/Mapping.java | 18 ++++ .../graphql/schema/diffing/SchemaDiffing.java | 85 ++++++++++++++++++- .../schema/diffing/SchemaGraphFactory.java | 67 +++++++++------ .../java/graphql/schema/diffing/Vertex.java | 1 + 6 files changed, 143 insertions(+), 45 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java index ad704e7e44..a996b06ca7 100644 --- a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java @@ -39,7 +39,7 @@ public void analyzeEdits(List editOperations) { } String fieldName = deletedVertex.getProperty("name"); // find the "dummy-type" vertex for this field - Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTICE))); + Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTEX))); Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index cb6327bb90..3a4e507f9f 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -49,7 +49,7 @@ */ public class HungarianAlgorithm { // changed by reduce - private final double[][] costMatrix; + public final double[][] costMatrix; // constant always private final int rows; @@ -57,7 +57,7 @@ public class HungarianAlgorithm { private final int dim; // the assigned workers,jobs for the result - private final int[] matchJobByWorker; + public final int[] matchJobByWorker; private final int[] matchWorkerByJob; // reset for each execute @@ -370,15 +370,4 @@ public int[] nextChild() { int[] result = Arrays.copyOf(matchJobByWorker, rows); return result; } - - public static void main(String[] args) { - double[][] c = new double[][]{{1, 33, 4}, {3, 5, 2}, {2, 4, 4}}; - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(c); - int[] result = hungarianAlgorithm.execute(); - System.out.println(Arrays.toString(result)); - for (int i = 0; i < c.length - 1; i++) { - result = hungarianAlgorithm.nextChild(); - System.out.println(Arrays.toString(result)); - } - } } \ No newline at end of file diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java index 196a992546..e7e000ae2f 100644 --- a/src/main/java/graphql/schema/diffing/Mapping.java +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class Mapping { private BiMap map = HashBiMap.create(); @@ -91,4 +92,21 @@ public Mapping extendMapping(Vertex source, Vertex target) { public BiMap getMap() { return map; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Mapping mapping = (Mapping) o; + return Objects.equals(map, mapping.map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 2d57f6b230..e2745e834a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -10,8 +10,16 @@ import static graphql.Assert.assertNotNull; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; +import static graphql.schema.diffing.SchemaGraphFactory.ENUM; +import static graphql.schema.diffing.SchemaGraphFactory.ENUM_VALUE; +import static graphql.schema.diffing.SchemaGraphFactory.FIELD; +import static graphql.schema.diffing.SchemaGraphFactory.INPUT_FIELD; import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; +import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; +import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; import static graphql.schema.diffing.SchemaGraphFactory.UNION; public class SchemaDiffing { @@ -267,6 +275,10 @@ private void generateChildren(MappingEntry parentEntry, List siblings = new ArrayList<>(); for (int child = 0; child < availableTargetVertices.size(); child++) { int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); + if (hungarianAlgorithm.costMatrix[0][assignments[0]] == Integer.MAX_VALUE) { + break; + } + double costMatrixSumSibling = getCostMatrixSum(costMatrix, assignments); double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; // System.out.println("lower bound: " + child + " : " + lowerBoundForPartialMappingSibling); @@ -286,13 +298,16 @@ private void generateChildren(MappingEntry parentEntry, sibling.assignments = assignments; sibling.availableTargetVertices = availableTargetVertices; + Set existingMappings = new LinkedHashSet<>(); // first child we add to the queue, otherwise save it for later if (child == 0) { +// System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); Mapping fullMapping = partialMapping.copy(); for (int i = 0; i < assignments.length; i++) { fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); } + assertTrue(fullMapping.size() == sourceGraph.size()); List editOperations = new ArrayList<>(); int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); @@ -435,6 +450,58 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so return cost; } + static Map> allowedTypeMappings = new LinkedHashMap<>(); + + static { + allowedTypeMappings.put(DUMMY_TYPE_VERTEX, Collections.singletonList(DUMMY_TYPE_VERTEX)); + allowedTypeMappings.put(SCALAR, Collections.singletonList(SCALAR)); +// allowedTypeMappings.put(ENUM, Collections.singletonList(ENUM)); +// allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE, OBJECT)); +// allowedTypeMappings.put(OBJECT, Arrays.asList(INTERFACE, OBJECT)); + allowedTypeMappings.put(ENUM_VALUE, Collections.singletonList(ENUM_VALUE)); +// allowedTypeMappings.put(FIELD, Collections.singletonList(FIELD)); +// allowedTypeMappings.put(ARGUMENT, Collections.singletonList(ARGUMENT)); + allowedTypeMappings.put(INPUT_FIELD, Collections.singletonList(INPUT_FIELD)); + } + + private boolean isMappingPossible(Vertex v, Vertex u) { + if (u.isArtificialNode()) { + return true; + } + if (v.isBuiltInType()) { + return u.isBuiltInType() && v.isEqualTo(u); + } +// return true; + List targetTypes = allowedTypeMappings.get(v.getType()); + if (targetTypes == null) { + return true; + } + boolean contains = targetTypes.contains(u.getType()); +// if (v.getType().equals(FIELD) && !contains) { +// System.out.println("bang"); +// } +// if (!contains) { +// System.out.println(v + " not to " + u); +// } + return contains; +// if (v.getType().equals(DUMMY_TYPE_VERTEX)) { +// if (!u.isArtificialNode() && !u.getType().equals(DUMMY_TYPE_VERTEX)) { +// return false; +// } +// } +// if (v.getType().equals(SchemaGraphFactory.SCALAR)) { +// if (!u.isArtificialNode() && !u.getType().equals(SCALAR)) { +// return false; +// } +// } +// if (v.getType().equals(SchemaGraphFactory.ENUM)) { +// if (!u.isArtificialNode() && !u.getType().equals(ENUM)) { +// return false; +// } +// } +// return true; + } + // lower bound mapping cost between for v -> u in respect to a partial mapping private double calcLowerBoundMappingCost(Vertex v, Vertex u, @@ -445,6 +512,12 @@ private double calcLowerBoundMappingCost(Vertex v, List partialMappingTargetList, Set partialMappingTargetSet ) { + if (!isMappingPossible(v, u)) { + return Integer.MAX_VALUE; + } + if (!isMappingPossible(u, v)) { + return Integer.MAX_VALUE; + } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges @@ -455,7 +528,6 @@ private double calcLowerBoundMappingCost(Vertex v, for (Edge edge : adjacentEdgesV) { // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices // or: at least one edge is part of the partial mapping -// if (nonMappedSourceVertices.contains(edge.getOne()) && nonMappedSourceVertices.contains(edge.getTwo())) { if (!partialMappingSourceSet.contains(edge.getOne()) && !partialMappingSourceSet.contains(edge.getTwo())) { multisetLabelsV.add(edge.getLabel()); } @@ -470,7 +542,9 @@ private double calcLowerBoundMappingCost(Vertex v, } } - + /** + * looking at all edges from x,vPrime and y,mappedVPrime + */ int anchoredVerticesCost = 0; for (int i = 0; i < partialMappingSourceList.size(); i++) { Vertex vPrime = partialMappingSourceList.get(i); @@ -486,8 +560,11 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); -// System.out.println("equalNodes : " + (equalNodes ? 0 : 1) + " editDistance " + (multiSetEditDistance / 2.0) + " anchored cost" + (anchoredVerticesCost)); - return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; + double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; +// if (v.getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTEX)) { +// System.out.println("result: " + result); +// } + return result; } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 80084e69e0..9f695633cc 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -18,11 +18,20 @@ public class SchemaGraphFactory { - public static final String DUMMY_TYPE_VERTICE = "__DUMMY_TYPE_VERTICE"; + public static final String DUMMY_TYPE_VERTEX = "__DUMMY_TYPE_VERTEX"; public static final String OBJECT = "Object"; public static final String INTERFACE = "Interface"; public static final String UNION = "Union"; public static final String INPUT_OBJECT = "InputObject"; + public static final String SCALAR = "Scalar"; + public static final String ENUM = "Enum"; + public static final String ENUM_VALUE = "EnumValue"; + public static final String APPLIED_DIRECTIVE = "AppliedDirective"; + public static final String FIELD = "Field"; + public static final String ARGUMENT = "Argument"; + public static final String APPLIED_ARGUMENT = "AppliedArgument"; + public static final String DIRECTIVE = "Directive"; + public static final String INPUT_FIELD = "InputField"; private int counter = 1; public SchemaGraph createGraph(GraphQLSchema schema) { @@ -102,7 +111,7 @@ public TraversalControl leave(TraverserContext context) { if (INPUT_OBJECT.equals(vertex.getType())) { handleInputObject(vertex, schemaGraph, schema); } - if ("AppliedDirective".equals(vertex.getType())) { + if (APPLIED_DIRECTIVE.equals(vertex.getType())) { handleAppliedDirective(vertex, schemaGraph, schema); } } @@ -128,7 +137,7 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); @@ -184,7 +193,7 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTICE, String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); @@ -210,10 +219,10 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume } private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex objectVertex = new Vertex("Object", String.valueOf(counter++)); + Vertex objectVertex = new Vertex(OBJECT, String.valueOf(counter++)); objectVertex.setBuiltInType(isIntrospectionNode); objectVertex.add("name", graphQLObjectType.getName()); - objectVertex.add("description", graphQLObjectType.getDescription()); + objectVertex.add("description", desc(graphQLObjectType.getDescription())); for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) { Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(newFieldVertex); @@ -225,10 +234,10 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr } private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex fieldVertex = new Vertex("Field", String.valueOf(counter++)); + Vertex fieldVertex = new Vertex(FIELD, String.valueOf(counter++)); fieldVertex.setBuiltInType(isIntrospectionNode); fieldVertex.add("name", graphQLFieldDefinition.getName()); - fieldVertex.add("description", graphQLFieldDefinition.getDescription()); + fieldVertex.add("description", desc(graphQLFieldDefinition.getDescription())); for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) { Vertex argumentVertex = newArgument(argument, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(argumentVertex); @@ -239,32 +248,32 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra } private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex("Argument", String.valueOf(counter++)); + Vertex vertex = new Vertex(ARGUMENT, String.valueOf(counter++)); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); - vertex.add("description", graphQLArgument.getDescription()); + vertex.add("description", desc(graphQLArgument.getDescription())); cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); return vertex; } private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex scalarVertex = new Vertex("Scalar", String.valueOf(counter++)); + Vertex scalarVertex = new Vertex(SCALAR, String.valueOf(counter++)); scalarVertex.setBuiltInType(isIntrospectionNode); if (ScalarInfo.isGraphqlSpecifiedScalar(scalarType.getName())) { scalarVertex.setBuiltInType(true); } scalarVertex.add("name", scalarType.getName()); - scalarVertex.add("description", scalarType.getDescription()); + scalarVertex.add("description", desc(scalarType.getDescription())); schemaGraph.addVertex(scalarVertex); schemaGraph.addType(scalarType.getName(), scalarVertex); cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); } private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex interfaceVertex = new Vertex("Interface", String.valueOf(counter++)); + Vertex interfaceVertex = new Vertex(INTERFACE, String.valueOf(counter++)); interfaceVertex.setBuiltInType(isIntrospectionNode); interfaceVertex.add("name", interfaceType.getName()); - interfaceVertex.add("description", interfaceType.getDescription()); + interfaceVertex.add("description", desc(interfaceType.getDescription())); for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode); schemaGraph.addVertex(newFieldVertex); @@ -276,12 +285,12 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema } private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex enumVertex = new Vertex("Enum", String.valueOf(counter++)); + Vertex enumVertex = new Vertex(ENUM, String.valueOf(counter++)); enumVertex.setBuiltInType(isIntrospectionNode); enumVertex.add("name", enumType.getName()); - enumVertex.add("description", enumType.getDescription()); + enumVertex.add("description", desc(enumType.getDescription())); for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { - Vertex enumValueVertex = new Vertex("EnumValue", String.valueOf(counter++)); + Vertex enumValueVertex = new Vertex(ENUM_VALUE, String.valueOf(counter++)); enumValueVertex.setBuiltInType(isIntrospectionNode); enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); @@ -294,20 +303,20 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean } private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex unionVertex = new Vertex("Union", String.valueOf(counter++)); + Vertex unionVertex = new Vertex(UNION, String.valueOf(counter++)); unionVertex.setBuiltInType(isIntrospectionNode); unionVertex.add("name", unionType.getName()); - unionVertex.add("description", unionType.getDescription()); + unionVertex.add("description", desc(unionType.getDescription())); schemaGraph.addVertex(unionVertex); schemaGraph.addType(unionType.getName(), unionVertex); cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); } private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex inputObjectVertex = new Vertex("InputObject", String.valueOf(counter++)); + Vertex inputObjectVertex = new Vertex(INPUT_OBJECT, String.valueOf(counter++)); inputObjectVertex.setBuiltInType(isIntrospectionNode); inputObjectVertex.add("name", inputObject.getName()); - inputObjectVertex.add("description", inputObject.getDescription()); + inputObjectVertex.add("description", desc(inputObject.getDescription())); for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) { Vertex newInputField = newInputField(inputObjectField, schemaGraph, isIntrospectionNode); Edge newEdge = new Edge(inputObjectVertex, newInputField); @@ -322,10 +331,10 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph schemaGraph) { for (GraphQLDirective appliedDirective : appliedDirectives) { - Vertex appliedDirectiveVertex = new Vertex("AppliedDirective", String.valueOf(counter++)); + Vertex appliedDirectiveVertex = new Vertex(APPLIED_DIRECTIVE, String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { - Vertex appliedArgumentVertex = new Vertex("AppliedArgument", String.valueOf(counter++)); + Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, String.valueOf(counter++)); appliedArgumentVertex.add("name", appliedArgument.getName()); appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); @@ -336,11 +345,11 @@ private void cratedAppliedDirectives(Vertex from, List applied } private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { - Vertex directiveVertex = new Vertex("Directive", String.valueOf(counter++)); + Vertex directiveVertex = new Vertex(DIRECTIVE, String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); boolean graphqlSpecified = DirectiveInfo.isGraphqlSpecifiedDirective(directive.getName()); directiveVertex.setBuiltInType(graphqlSpecified); - directiveVertex.add("description", directive.getDescription()); + directiveVertex.add("description", desc(directive.getDescription())); for (GraphQLArgument argument : directive.getArguments()) { Vertex argumentVertex = newArgument(argument, schemaGraph, graphqlSpecified); schemaGraph.addVertex(argumentVertex); @@ -351,12 +360,16 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { } private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex("InputField", String.valueOf(counter++)); + Vertex vertex = new Vertex(INPUT_FIELD, String.valueOf(counter++)); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); - vertex.add("description", inputField.getDescription()); + vertex.add("description", desc(inputField.getDescription())); cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); return vertex; } + private String desc(String desc) { + return desc != null ? desc.replace("\n", "\\n") : null; + } + } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 7b36ca0a27..44df56877d 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -76,4 +76,5 @@ public String toString() { ", builtInType='" + builtInType + '\'' + '}'; } + } From 8481583ec78b84bba1e2071113c9e884dbf0770f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 17 Dec 2021 16:58:50 +1100 Subject: [PATCH 025/294] cleanup --- .../graphql/schema/diffing/SchemaDiffing.java | 67 ++++++------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index e2745e834a..79f7a499d8 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,14 +1,25 @@ package graphql.schema.diffing; -import com.google.common.collect.*; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; -import graphql.schema.*; - -import java.util.*; +import graphql.schema.GraphQLSchema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import static graphql.Assert.assertNotNull; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; @@ -40,11 +51,9 @@ public MappingEntry() { } - // target vertices which the fist `level` vertices of source graph are mapped to Mapping partialMapping = new Mapping(); int level; double lowerBoundCost; -// Set candidates = new LinkedHashSet<>(); } SchemaGraph sourceGraph; @@ -53,7 +62,6 @@ public MappingEntry() { public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) { sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); -// System.out.println(GraphPrinter.print(sourceGraph)); return diffImpl(sourceGraph, targetGraph); } @@ -63,7 +71,6 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph } List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { - // we assert here that the graphs have the same size. The algorithm depends on it if (sourceGraph.size() < targetGraph.size()) { sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); } else if (sourceGraph.size() > targetGraph.size()) { @@ -96,7 +103,6 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { System.out.println((counter) + " entry at level"); } if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { -// System.out.println("skipping!"); continue; } if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { @@ -140,13 +146,11 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { for (Edge edge : sourceGraph.getEdges()) { edgesWeights.put(edge, infrequencyWeightForEdge(edge, targetGraph)); } - // start with the vertex with largest total weight List result = new ArrayList<>(); ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); nextCandidates.sort(Comparator.comparingInt(o -> totalWeightWithAdjacentEdges(sourceGraph, o, vertexWeights, edgesWeights))); // System.out.println("0: " + totalWeight(sourceGraph, nextCandidates.get(0), vertexWeights, edgesWeights)); // System.out.println("last: " + totalWeight(sourceGraph, nextCandidates.get(nextCandidates.size() - 1), vertexWeights, edgesWeights)); -// // starting with the one with largest totalWeight: Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); result.add(curVertex); nextCandidates.remove(nextCandidates.size() - 1); @@ -169,7 +173,6 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { nextCandidates.remove(nextOneIndex); } System.out.println(result); -// System.out.println(nextCandidates); sourceGraph.setVertices(result); } @@ -455,12 +458,12 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so static { allowedTypeMappings.put(DUMMY_TYPE_VERTEX, Collections.singletonList(DUMMY_TYPE_VERTEX)); allowedTypeMappings.put(SCALAR, Collections.singletonList(SCALAR)); -// allowedTypeMappings.put(ENUM, Collections.singletonList(ENUM)); -// allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE, OBJECT)); -// allowedTypeMappings.put(OBJECT, Arrays.asList(INTERFACE, OBJECT)); + allowedTypeMappings.put(ENUM, Collections.singletonList(ENUM)); + allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE, OBJECT)); + allowedTypeMappings.put(OBJECT, Arrays.asList(INTERFACE, OBJECT)); allowedTypeMappings.put(ENUM_VALUE, Collections.singletonList(ENUM_VALUE)); -// allowedTypeMappings.put(FIELD, Collections.singletonList(FIELD)); -// allowedTypeMappings.put(ARGUMENT, Collections.singletonList(ARGUMENT)); + allowedTypeMappings.put(FIELD, Collections.singletonList(FIELD)); + allowedTypeMappings.put(ARGUMENT, Collections.singletonList(ARGUMENT)); allowedTypeMappings.put(INPUT_FIELD, Collections.singletonList(INPUT_FIELD)); } @@ -477,29 +480,7 @@ private boolean isMappingPossible(Vertex v, Vertex u) { return true; } boolean contains = targetTypes.contains(u.getType()); -// if (v.getType().equals(FIELD) && !contains) { -// System.out.println("bang"); -// } -// if (!contains) { -// System.out.println(v + " not to " + u); -// } return contains; -// if (v.getType().equals(DUMMY_TYPE_VERTEX)) { -// if (!u.isArtificialNode() && !u.getType().equals(DUMMY_TYPE_VERTEX)) { -// return false; -// } -// } -// if (v.getType().equals(SchemaGraphFactory.SCALAR)) { -// if (!u.isArtificialNode() && !u.getType().equals(SCALAR)) { -// return false; -// } -// } -// if (v.getType().equals(SchemaGraphFactory.ENUM)) { -// if (!u.isArtificialNode() && !u.getType().equals(ENUM)) { -// return false; -// } -// } -// return true; } // lower bound mapping cost between for v -> u in respect to a partial mapping @@ -560,11 +541,7 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; -// if (v.getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTEX)) { -// System.out.println("result: " + result); -// } - return result; + return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; } } From 986cbe6ec652d18f66c0596b41cd3dbba0865865 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 20 Dec 2021 01:00:00 +1100 Subject: [PATCH 026/294] fix bug regarding input object fields, tests --- .../graphql/schema/diffing/GraphPrinter.java | 2 - .../graphql/schema/diffing/SchemaDiffing.java | 33 +++++++++--- .../schema/diffing/SchemaGraphFactory.java | 1 + .../graphql/schema/diffing/dot/Dotfile.java | 30 +++++------ src/test/groovy/graphql/TestUtil.groovy | 20 ++++---- .../schema/diffing/SchemaDiffingTest.groovy | 51 ++++++++++++++++--- 6 files changed, 96 insertions(+), 41 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/GraphPrinter.java b/src/main/java/graphql/schema/diffing/GraphPrinter.java index d09e67244c..b5ffc28a56 100644 --- a/src/main/java/graphql/schema/diffing/GraphPrinter.java +++ b/src/main/java/graphql/schema/diffing/GraphPrinter.java @@ -14,8 +14,6 @@ public static String print(SchemaGraph schemaGraph) { dotfile.addNode("V" + Integer.toHexString(vertex.hashCode()), name, "blue"); } for (Edge edge : schemaGraph.getEdges()) { - String nameOne = edge.getOne().get("name"); - String nameTwo = edge.getTwo().get("name"); dotfile.addEdge("V" + Integer.toHexString(edge.getOne().hashCode()), "V" + Integer.toHexString(edge.getTwo().hashCode()), edge.getLabel()); } return dotfile.print(); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 79f7a499d8..d710591fb5 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -21,7 +21,10 @@ import java.util.stream.Collectors; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; import static graphql.schema.diffing.SchemaGraphFactory.ENUM; import static graphql.schema.diffing.SchemaGraphFactory.ENUM_VALUE; @@ -80,6 +83,11 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); sortSourceGraph(sourceGraph, targetGraph); +// if (true) { +// String print = GraphPrinter.print(sourceGraph); +// System.out.println(print); +// return Collections.emptyList(); +// } AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); @@ -95,7 +103,6 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { }); queue.add(new MappingEntry()); int counter = 0; - while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); // System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); @@ -274,7 +281,7 @@ private void generateChildren(MappingEntry parentEntry, HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - // generate all childrens (which are siblings to each other) + // generate all children (which are siblings to each other) List siblings = new ArrayList<>(); for (int child = 0; child < availableTargetVertices.size(); child++) { int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); @@ -287,6 +294,7 @@ private void generateChildren(MappingEntry parentEntry, // System.out.println("lower bound: " + child + " : " + lowerBoundForPartialMappingSibling); int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); +// System.out.println("adding new mapping " + v_i + " => " + bestExtensionTargetVertexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); if (lowerBoundForPartialMappingSibling == parentEntry.lowerBoundCost) { @@ -304,7 +312,7 @@ private void generateChildren(MappingEntry parentEntry, Set existingMappings = new LinkedHashSet<>(); // first child we add to the queue, otherwise save it for later if (child == 0) { -// System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); +// System.out.println("adding new child entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); Mapping fullMapping = partialMapping.copy(); for (int i = 0; i < assignments.length; i++) { @@ -340,7 +348,7 @@ private void getSibling( MappingEntry sibling = mappingEntry.mappingEntriesSiblings.get(0); if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { -// System.out.println("adding new entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); +// System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); mappingEntry.mappingEntriesSiblings.remove(0); @@ -393,7 +401,13 @@ private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertic private List getDebugMap(Mapping mapping) { List result = new ArrayList<>(); +// if (mapping.size() > 0) { +// result.add(mapping.getSource(mapping.size() - 1).getType() + " -> " + mapping.getTarget(mapping.size() - 1).getType()); +// } for (Map.Entry entry : mapping.getMap().entrySet()) { +// if (!entry.getKey().getType().equals(entry.getValue().getType())) { +// result.add(entry.getKey().getType() + "->" + entry.getValue().getType()); +// } result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); } return result; @@ -459,16 +473,21 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so allowedTypeMappings.put(DUMMY_TYPE_VERTEX, Collections.singletonList(DUMMY_TYPE_VERTEX)); allowedTypeMappings.put(SCALAR, Collections.singletonList(SCALAR)); allowedTypeMappings.put(ENUM, Collections.singletonList(ENUM)); - allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE, OBJECT)); - allowedTypeMappings.put(OBJECT, Arrays.asList(INTERFACE, OBJECT)); allowedTypeMappings.put(ENUM_VALUE, Collections.singletonList(ENUM_VALUE)); + allowedTypeMappings.put(OBJECT, Arrays.asList(OBJECT)); + allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE)); allowedTypeMappings.put(FIELD, Collections.singletonList(FIELD)); allowedTypeMappings.put(ARGUMENT, Collections.singletonList(ARGUMENT)); + allowedTypeMappings.put(INPUT_OBJECT, Collections.singletonList(INPUT_OBJECT)); allowedTypeMappings.put(INPUT_FIELD, Collections.singletonList(INPUT_FIELD)); + allowedTypeMappings.put(UNION, Collections.singletonList(UNION)); + allowedTypeMappings.put(APPLIED_DIRECTIVE, Collections.singletonList(APPLIED_DIRECTIVE)); + allowedTypeMappings.put(APPLIED_ARGUMENT, Collections.singletonList(APPLIED_ARGUMENT)); + allowedTypeMappings.put(DIRECTIVE, Collections.singletonList(DIRECTIVE)); } private boolean isMappingPossible(Vertex v, Vertex u) { - if (u.isArtificialNode()) { + if (u.isArtificialNode() || v.isArtificialNode()) { return true; } if (v.isBuiltInType()) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 9f695633cc..864c78dfab 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -361,6 +361,7 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) { Vertex vertex = new Vertex(INPUT_FIELD, String.valueOf(counter++)); + schemaGraph.addVertex(vertex); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); vertex.add("description", desc(inputField.getDescription())); diff --git a/src/main/java/graphql/schema/diffing/dot/Dotfile.java b/src/main/java/graphql/schema/diffing/dot/Dotfile.java index ac28a549bb..6e7e51dfc4 100644 --- a/src/main/java/graphql/schema/diffing/dot/Dotfile.java +++ b/src/main/java/graphql/schema/diffing/dot/Dotfile.java @@ -59,7 +59,7 @@ public void addNode(Node node) { private List nodes = new ArrayList<>(); private List edges = new ArrayList<>(); - private List subGraphs = new ArrayList<>(); +// private List subGraphs = new ArrayList<>(); public void addNode(Node node) { @@ -78,9 +78,9 @@ public void addEdge(Edge e) { edges.add(e); } - public void addSubgraph(SubGraph subGraph) { - subGraphs.add(subGraph); - } +// public void addSubgraph(SubGraph subGraph) { +// subGraphs.add(subGraph); +// } public String getId() { return ""; @@ -95,17 +95,17 @@ public String print() { for (Edge edge : edges) { result.append(edge.from).append(" -- ").append(edge.to).append("[label=\"").append(edge.label).append("\"];\n"); } - for (SubGraph subGraph : subGraphs) { - result.append("subgraph cluster_").append(subGraph.id).append("{\n").append("label=\"").append(subGraph.label).append("\";\n"); - for (Node node : subGraph.nodes) { - result.append(node.id).append("[label=\"").append(node.label).append("\" color=").append(node.color).append(" style=filled").append("];\n"); - } - for (Edge edge : subGraph.edges) { - result.append(edge.from).append(" -- ").append(edge.to).append("[label=\"").append(edge.label).append("\"];\n"); - } - result.append("}"); - - } +// for (SubGraph subGraph : subGraphs) { +// result.append("subgraph cluster_").append(subGraph.id).append("{\n").append("label=\"").append(subGraph.label).append("\";\n"); +// for (Node node : subGraph.nodes) { +// result.append(node.id).append("[label=\"").append(node.label).append("\" color=").append(node.color).append(" style=filled").append("];\n"); +// } +// for (Edge edge : subGraph.edges) { +// result.append(edge.from).append(" -- ").append(edge.to).append("[label=\"").append(edge.label).append("\"];\n"); +// } +// result.append("}"); +// +// } // result.append(explanation()); result.append("}"); return result.toString(); diff --git a/src/test/groovy/graphql/TestUtil.groovy b/src/test/groovy/graphql/TestUtil.groovy index aec2bb90ec..45301413ce 100644 --- a/src/test/groovy/graphql/TestUtil.groovy +++ b/src/test/groovy/graphql/TestUtil.groovy @@ -100,18 +100,18 @@ class TestUtil { schema(specReader, mockRuntimeWiring) } - static GraphQLSchema schema(String spec, RuntimeWiring runtimeWiring) { - schema(new StringReader(spec), runtimeWiring) + static GraphQLSchema schema(String spec, RuntimeWiring runtimeWiring, boolean commentsAsDescription = true) { + schema(new StringReader(spec), runtimeWiring, commentsAsDescription) } static GraphQLSchema schema(InputStream specStream, RuntimeWiring runtimeWiring) { schema(new InputStreamReader(specStream), runtimeWiring) } - static GraphQLSchema schema(Reader specReader, RuntimeWiring runtimeWiring) { + static GraphQLSchema schema(Reader specReader, RuntimeWiring runtimeWiring, boolean commentsAsDescription = true) { try { def registry = new SchemaParser().parse(specReader) - def options = SchemaGenerator.Options.defaultOptions() + def options = SchemaGenerator.Options.defaultOptions().useCommentsAsDescriptions(commentsAsDescription) return new SchemaGenerator().makeExecutableSchema(options, registry, runtimeWiring) } catch (SchemaProblem e) { assert false: "The schema could not be compiled : ${e}" @@ -188,12 +188,12 @@ class TestUtil { static GraphQLScalarType mockScalar(ScalarTypeDefinition definition) { newScalar() - .name(definition.getName()) - .description(definition.getDescription() == null ? null : definition.getDescription().getContent()) - .coercing(mockCoercing()) - .replaceDirectives(definition.getDirectives().stream().map({ mockDirective(it.getName()) }).collect(Collectors.toList())) - .definition(definition) - .build() + .name(definition.getName()) + .description(definition.getDescription() == null ? null : definition.getDescription().getContent()) + .coercing(mockCoercing()) + .replaceDirectives(definition.getDirectives().stream().map({ mockDirective(it.getName()) }).collect(Collectors.toList())) + .definition(definition) + .build() } static GraphQLDirective mockDirective(String name) { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 0365227301..b31dab605e 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -200,13 +200,9 @@ class SchemaDiffingTest extends Specification { then: /** - * 1. Change type Pet into Interface Pet - * 2,3,4: Insert Object Dog, Insert Field name, Insert __DUMMY_TYPE_VERTICE - * 5. Insert Edge from Object Dog to Field name - * 6,7 Insert Edge from Field name to DUMMY_TYPE_VERTICE to Scalar String - * 8. Insert 'implements' Edge from Object Pet to Interface Pet + * If we would allow to map Object to Interface this would have a result of 8 */ - diff.size() == 8 + diff.size() == 10 } @@ -385,7 +381,7 @@ class SchemaDiffingTest extends Specification { """) when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2,false) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) then: diff.size() == 58 @@ -693,6 +689,47 @@ class SchemaDiffingTest extends Specification { } + def "adding an argument "() { + given: + def schema1 = schema(""" + type Query { + foo: String + } + """) + def schema2 = schema(""" + type Query { + foo(arg: Int): String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 4 + } + + def "changing an argument "() { + given: + def schema1 = schema(""" + type Query { + foo(arg: Int): String + } + """) + def schema2 = schema(""" + type Query { + foo(arg2: Boolean): String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 4 + } + + // def "test example schema"() { // given: // def source = buildSourceGraph() From 32a650171938cb811f6f45d28e99cacb9cc7fc63 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 20 Dec 2021 10:03:54 +1100 Subject: [PATCH 027/294] performance work --- .../graphql/schema/diffing/SchemaDiffing.java | 90 +++++++++++++++++-- .../graphql/schema/diffing/SchemaGraph.java | 20 +++++ .../schema/diffing/SchemaDiffingTest.groovy | 31 ++++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index d710591fb5..7912eafb16 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -4,6 +4,7 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; +import graphql.Assert; import graphql.schema.GraphQLSchema; import java.util.ArrayList; @@ -35,6 +36,7 @@ import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; import static graphql.schema.diffing.SchemaGraphFactory.UNION; +import static java.lang.String.format; public class SchemaDiffing { @@ -486,20 +488,98 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so allowedTypeMappings.put(DIRECTIVE, Collections.singletonList(DIRECTIVE)); } - private boolean isMappingPossible(Vertex v, Vertex u) { + private Map forcedMatchingCache = new LinkedHashMap<>(); + + private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet) { + // deletion and inserting of vertices if (u.isArtificialNode() || v.isArtificialNode()) { return true; } + // build in types need to match exactly built in types: not allowing to change + // Introspection API to change here if (v.isBuiltInType()) { return u.isBuiltInType() && v.isEqualTo(u); } -// return true; List targetTypes = allowedTypeMappings.get(v.getType()); if (targetTypes == null) { return true; } boolean contains = targetTypes.contains(u.getType()); - return contains; + if (contains) { + if (isNamedType(v.getType())) { + Vertex targetVertex = targetGraph.getType(v.get("name")); + if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { + return u == targetVertex; + } + } + Vertex forcedMatch = forcedMatchingCache.get(v); + if (forcedMatch != null && partialMappingTargetSet.contains(forcedMatch)) { + return forcedMatch == u; + } + if (FIELD.equals(v.getType())) { + Vertex sourceFieldsContainer = getFieldsContainer(v, sourceGraph); + Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); + if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { + Vertex targetFieldWithSameParentAndSameName = getField(targetFieldsContainerWithSameName, v.get("name"), targetGraph); + if (targetFieldWithSameParentAndSameName != null) { + forcedMatchingCache.put(v, u); + return u == targetFieldWithSameParentAndSameName; + } + } + } + if (ENUM_VALUE.equals(v.getType())) { + Vertex enumVertex = getEnum(v, sourceGraph); + Vertex targetEnumWithSameName = targetGraph.getType(enumVertex.get("name")); + if (targetEnumWithSameName != null) { + Vertex targetEnumValueWithSameParentAndSameName = getEnumValue(targetEnumWithSameName, v.get("name"), targetGraph); + if (targetEnumValueWithSameParentAndSameName != null) { + forcedMatchingCache.put(v, u); + return u == targetEnumValueWithSameParentAndSameName; + } + } + } + return true; + } else { + return false; + } + } + + private Vertex getField(Vertex fieldsContainer, String fieldName, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(fieldsContainer, v -> v.getType().equals(FIELD) && fieldName.equals(v.get("name"))); + assertTrue(adjacentVertices.size() <= 1); + return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); + } + + private Vertex getEnumValue(Vertex enumVertex, String valueName, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(enumVertex, v -> valueName.equals(v.get("name"))); + assertTrue(adjacentVertices.size() <= 1); + return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); + } + + private Vertex getFieldsContainer(Vertex field, SchemaGraph schemaGraph) { + List adjacentEdges = schemaGraph.getAdjacentEdges(field); + for (Edge edge : adjacentEdges) { + Vertex adjacentVertex; + if (edge.getOne() == field) { + adjacentVertex = edge.getTwo(); + } else { + adjacentVertex = edge.getOne(); + } + if (adjacentVertex.getType().equals(OBJECT) || adjacentVertex.getType().equals(INTERFACE)) { + return adjacentVertex; + } + } + return Assert.assertShouldNeverHappen("No fields container found for ", field); + } + + private Vertex getEnum(Vertex enumValue, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); + assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for value %s", enumValue)); + return adjacentVertices.get(0); + } + + private boolean isNamedType(String type) { + return Arrays.asList(OBJECT, INTERFACE, INPUT_OBJECT, ENUM, UNION, SCALAR).contains(type); } // lower bound mapping cost between for v -> u in respect to a partial mapping @@ -512,10 +592,10 @@ private double calcLowerBoundMappingCost(Vertex v, List partialMappingTargetList, Set partialMappingTargetSet ) { - if (!isMappingPossible(v, u)) { + if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet)) { return Integer.MAX_VALUE; } - if (!isMappingPossible(u, v)) { + if (!isMappingPossible(u, v, targetGraph, sourceGraph, partialMappingSourceSet)) { return Integer.MAX_VALUE; } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index e7c9aa4402..cc560b13c0 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -43,6 +43,26 @@ public List getAdjacentEdges(Vertex from) { return new ArrayList<>(edgeByVertexPair.row(from).values()); } + public List getAdjacentVertices(Vertex from) { + return getAdjacentVertices(from, x -> true); + } + + public List getAdjacentVertices(Vertex from, Predicate predicate) { + List result = new ArrayList<>(); + for (Edge edge : edgeByVertexPair.row(from).values()) { + Vertex v; + if (edge.getOne() == from) { + v = edge.getTwo(); + } else { + v = edge.getOne(); + } + if (predicate.test(v)) { + result.add(v); + } + } + return result; + } + public Edge getSingleAdjacentEdge(Vertex from, Predicate predicate) { for (Edge edge : edgeByVertexPair.row(from).values()) { if (predicate.test(edge)) { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index b31dab605e..98ca1134bb 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -639,7 +639,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - diff.size() == 8 + diff.size() == 9 } @@ -729,6 +729,35 @@ class SchemaDiffingTest extends Specification { operations.size() == 4 } + def "adding enum value"() { + given: + def schema1 = schema(""" + type Query { + foo: Foo + } + enum Foo { + V1 + V2 + } + """) + def schema2 = schema(""" + type Query { + foo: Foo + } + enum Foo { + V1 + V2 + V3 + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 2 + } + // def "test example schema"() { // given: From 7829b0361ccc9e1823d448d2a68b7bafd06b1cf3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 20 Dec 2021 12:53:01 +1100 Subject: [PATCH 028/294] performance work --- .../graphql/schema/diffing/SchemaDiffing.java | 158 +++++++++++++----- 1 file changed, 113 insertions(+), 45 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 7912eafb16..f40b2a2888 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -4,7 +4,6 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; -import graphql.Assert; import graphql.schema.GraphQLSchema; import java.util.ArrayList; @@ -271,6 +270,8 @@ private void generateChildren(MappingEntry parentEntry, Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); // costMatrix[0] is the row for v_i + int counter = 0; + int blockedCounter = 0; for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int j = 0; @@ -278,8 +279,15 @@ private void generateChildren(MappingEntry parentEntry, double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); costMatrix[i - level + 1][j] = cost; j++; + counter++; + if (cost == Integer.MAX_VALUE) { + blockedCounter++; + } else { +// System.out.println("not blocked " + v.getType()); + } } } +// System.out.println("counter: " + counter + " vs " + blockedCounter + " perc: " + (blockedCounter / (double) counter)); HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); @@ -505,43 +513,107 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S return true; } boolean contains = targetTypes.contains(u.getType()); - if (contains) { - if (isNamedType(v.getType())) { - Vertex targetVertex = targetGraph.getType(v.get("name")); - if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { - return u == targetVertex; - } - } - Vertex forcedMatch = forcedMatchingCache.get(v); - if (forcedMatch != null && partialMappingTargetSet.contains(forcedMatch)) { - return forcedMatch == u; + if (!contains) { + return false; + } + if (isNamedType(v.getType())) { + Vertex targetVertex = targetGraph.getType(v.get("name")); + if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { + return u == targetVertex; } - if (FIELD.equals(v.getType())) { - Vertex sourceFieldsContainer = getFieldsContainer(v, sourceGraph); - Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); - if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { - Vertex targetFieldWithSameParentAndSameName = getField(targetFieldsContainerWithSameName, v.get("name"), targetGraph); - if (targetFieldWithSameParentAndSameName != null) { - forcedMatchingCache.put(v, u); - return u == targetFieldWithSameParentAndSameName; + } + Vertex forcedMatch = forcedMatchingCache.get(v); + if (forcedMatch != null) { + return forcedMatch == u; + } + + if (DUMMY_TYPE_VERTEX.equals(v.getType())) { + List adjacentVertices = sourceGraph.getAdjacentVertices(v); + for (Vertex vertex : adjacentVertices) { + if (vertex.getType().equals(FIELD)) { + Vertex matchingTargetField = findMatchingTargetField(vertex, sourceGraph, targetGraph); + if (matchingTargetField != null) { + Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); + forcedMatchingCache.put(v, dummyTypeVertex); + return u == dummyTypeVertex; } - } - } - if (ENUM_VALUE.equals(v.getType())) { - Vertex enumVertex = getEnum(v, sourceGraph); - Vertex targetEnumWithSameName = targetGraph.getType(enumVertex.get("name")); - if (targetEnumWithSameName != null) { - Vertex targetEnumValueWithSameParentAndSameName = getEnumValue(targetEnumWithSameName, v.get("name"), targetGraph); - if (targetEnumValueWithSameParentAndSameName != null) { - forcedMatchingCache.put(v, u); - return u == targetEnumValueWithSameParentAndSameName; + } else if (vertex.getType().equals(INPUT_FIELD)) { + Vertex matchingTargetInputField = findMatchingTargetInputField(vertex, sourceGraph, targetGraph); + if (matchingTargetInputField != null) { + Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); + forcedMatchingCache.put(v, dummyTypeVertex); + return u == dummyTypeVertex; } } } - return true; - } else { - return false; + + } + if (INPUT_FIELD.equals(v.getType())) { + Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); + if (matchingTargetInputField != null) { + forcedMatchingCache.put(v, matchingTargetInputField); + return u == matchingTargetInputField; + } + } + if (FIELD.equals(v.getType())) { + Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); + if (matchingTargetField != null) { + forcedMatchingCache.put(v, matchingTargetField); + return u == matchingTargetField; + } + } + if (ENUM_VALUE.equals(v.getType())) { + Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); + if (matchingTargetEnumValue != null) { + forcedMatchingCache.put(v, matchingTargetEnumValue); + return u == matchingTargetEnumValue; + } + } + return true; + + } + + private Vertex getDummyTypeVertex(Vertex vertex, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(vertex, v -> v.getType().equals(DUMMY_TYPE_VERTEX)); + assertTrue(adjacentVertices.size() == 1); + return adjacentVertices.get(0); + } + + private Vertex findMatchingEnumValue(Vertex enumValue, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex enumVertex = getEnum(enumValue, sourceGraph); + Vertex targetEnumWithSameName = targetGraph.getType(enumVertex.get("name")); + if (targetEnumWithSameName != null) { + Vertex matchingTarget = getEnumValue(targetEnumWithSameName, enumValue.get("name"), targetGraph); + return matchingTarget; + } + return null; + } + + private Vertex findMatchingTargetInputField(Vertex inputField, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex sourceInputObject = getInputFieldsObject(inputField, sourceGraph); + Vertex targetInputObject = targetGraph.getType(sourceInputObject.get("name")); + if (targetInputObject != null) { + Vertex matchingInputField = getInputField(targetInputObject, inputField.get("name"), targetGraph); + return matchingInputField; } + return null; + + } + + private Vertex findMatchingTargetField(Vertex field, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex sourceFieldsContainer = getFieldsContainer(field, sourceGraph); + Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); + if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { + Vertex mathchingField = getField(targetFieldsContainerWithSameName, field.get("name"), targetGraph); + return mathchingField; + } + return null; + } + + private Vertex getInputField(Vertex inputObject, String fieldName, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(inputObject, v -> v.getType().equals(INPUT_FIELD) && fieldName.equals(v.get("name"))); + assertTrue(adjacentVertices.size() <= 1); + return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); } private Vertex getField(Vertex fieldsContainer, String fieldName, SchemaGraph schemaGraph) { @@ -556,20 +628,16 @@ private Vertex getEnumValue(Vertex enumVertex, String valueName, SchemaGraph sch return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); } + private Vertex getInputFieldsObject(Vertex inputField, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); + assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", inputField)); + return adjacentVertices.get(0); + } + private Vertex getFieldsContainer(Vertex field, SchemaGraph schemaGraph) { - List adjacentEdges = schemaGraph.getAdjacentEdges(field); - for (Edge edge : adjacentEdges) { - Vertex adjacentVertex; - if (edge.getOne() == field) { - adjacentVertex = edge.getTwo(); - } else { - adjacentVertex = edge.getOne(); - } - if (adjacentVertex.getType().equals(OBJECT) || adjacentVertex.getType().equals(INTERFACE)) { - return adjacentVertex; - } - } - return Assert.assertShouldNeverHappen("No fields container found for ", field); + List adjacentVertices = schemaGraph.getAdjacentVertices(field, vertex -> vertex.getType().equals(OBJECT) || vertex.getType().equals(INTERFACE)); + assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", field)); + return adjacentVertices.get(0); } private Vertex getEnum(Vertex enumValue, SchemaGraph schemaGraph) { From b22f505075c820a3fe59a65def9d76b5d8c34aa0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 4 Jan 2022 12:51:15 +1100 Subject: [PATCH 029/294] mino --- src/main/java/graphql/schema/diffing/SchemaDiffing.java | 2 +- .../java/graphql/schema/diffing/SchemaGraphFactory.java | 6 ++++++ .../groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 1 - 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index f40b2a2888..700b543a8b 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -83,7 +83,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { assertTrue(sourceGraph.size() == targetGraph.size()); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); - sortSourceGraph(sourceGraph, targetGraph); +// sortSourceGraph(sourceGraph, targetGraph); // if (true) { // String print = GraphPrinter.print(sourceGraph); // System.out.println(print); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 864c78dfab..ab9a49a95f 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -32,6 +32,9 @@ public class SchemaGraphFactory { public static final String APPLIED_ARGUMENT = "AppliedArgument"; public static final String DIRECTIVE = "Directive"; public static final String INPUT_FIELD = "InputField"; + public static final String VALUE = "Value"; + public static final String DIRECTIVE_LOCATION = "DirectiveLocation"; + private int counter = 1; public SchemaGraph createGraph(GraphQLSchema schema) { @@ -264,6 +267,7 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, bo } scalarVertex.add("name", scalarType.getName()); scalarVertex.add("description", desc(scalarType.getDescription())); + scalarVertex.add("specifiedByUrl", scalarType.getSpecifiedByUrl()); schemaGraph.addVertex(scalarVertex); schemaGraph.addType(scalarType.getName(), scalarVertex); cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); @@ -347,6 +351,8 @@ private void cratedAppliedDirectives(Vertex from, List applied private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { Vertex directiveVertex = new Vertex(DIRECTIVE, String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); + directiveVertex.add("repeatable", directive.isRepeatable()); + directiveVertex.add("locations", directive.validLocations()); boolean graphqlSpecified = DirectiveInfo.isGraphqlSpecifiedDirective(directive.getName()); directiveVertex.setBuiltInType(graphqlSpecified); directiveVertex.add("description", desc(directive.getDescription())); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 98ca1134bb..2971a6c351 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -206,7 +206,6 @@ class SchemaDiffingTest extends Specification { } - @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) From 9baa577e95b06850a37d5a6d9e320e365374c502 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 4 Jan 2022 13:14:33 +1100 Subject: [PATCH 030/294] performance work --- .../graphql/schema/diffing/SchemaDiffing.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 700b543a8b..0dc3a357ee 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -497,17 +497,17 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so } private Map forcedMatchingCache = new LinkedHashMap<>(); + private Map forcedMatchingNegativeCache = new LinkedHashMap<>(); private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet) { + Vertex forcedMatch = forcedMatchingCache.get(v); + if (forcedMatch != null) { + return forcedMatch == u; + } // deletion and inserting of vertices if (u.isArtificialNode() || v.isArtificialNode()) { return true; } - // build in types need to match exactly built in types: not allowing to change - // Introspection API to change here - if (v.isBuiltInType()) { - return u.isBuiltInType() && v.isEqualTo(u); - } List targetTypes = allowedTypeMappings.get(v.getType()); if (targetTypes == null) { return true; @@ -522,10 +522,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S return u == targetVertex; } } - Vertex forcedMatch = forcedMatchingCache.get(v); - if (forcedMatch != null) { - return forcedMatch == u; - } if (DUMMY_TYPE_VERTEX.equals(v.getType())) { List adjacentVertices = sourceGraph.getAdjacentVertices(v); @@ -535,6 +531,7 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S if (matchingTargetField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); + forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } else if (vertex.getType().equals(INPUT_FIELD)) { @@ -542,6 +539,7 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S if (matchingTargetInputField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); + forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } @@ -552,6 +550,7 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); if (matchingTargetInputField != null) { forcedMatchingCache.put(v, matchingTargetInputField); + forcedMatchingCache.put(matchingTargetInputField, v); return u == matchingTargetInputField; } } @@ -559,6 +558,7 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); if (matchingTargetField != null) { forcedMatchingCache.put(v, matchingTargetField); + forcedMatchingCache.put(matchingTargetField, v); return u == matchingTargetField; } } @@ -566,6 +566,7 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); if (matchingTargetEnumValue != null) { forcedMatchingCache.put(v, matchingTargetEnumValue); + forcedMatchingCache.put(matchingTargetEnumValue, v); return u == matchingTargetEnumValue; } } From 32b4f9b8cc7ae9582a4f849286246482a5b54ccb Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 4 Jan 2022 13:19:47 +1100 Subject: [PATCH 031/294] performance work --- src/main/java/graphql/schema/diffing/SchemaDiffing.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 0dc3a357ee..926a170121 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -519,6 +519,10 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S if (isNamedType(v.getType())) { Vertex targetVertex = targetGraph.getType(v.get("name")); if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { + if (u == targetVertex) { + forcedMatchingCache.put(v, targetVertex); + forcedMatchingCache.put(targetVertex, v); + } return u == targetVertex; } } From ac01af834762e7e1f5b8e808c8e5a71448b4edba Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 4 Jan 2022 14:42:49 +1100 Subject: [PATCH 032/294] parallel version --- .../schema/diffing/HungarianAlgorithm.java | 72 ++++++++++--------- .../graphql/schema/diffing/SchemaDiffing.java | 67 +++++++++-------- 2 files changed, 73 insertions(+), 66 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index 3a4e507f9f..ed53a437cf 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import com.google.common.util.concurrent.AtomicDoubleArray; + import java.util.Arrays; /* Copyright (c) 2012 Kevin L. Stern @@ -49,7 +51,7 @@ */ public class HungarianAlgorithm { // changed by reduce - public final double[][] costMatrix; + public final AtomicDoubleArray[] costMatrix; // constant always private final int rows; @@ -81,29 +83,29 @@ public class HungarianAlgorithm { * irregular in the sense that all rows must be the same length; in * addition, all entries must be non-infinite numbers. */ - public HungarianAlgorithm(double[][] costMatrix) { - this.dim = Math.max(costMatrix.length, costMatrix[0].length); + public HungarianAlgorithm(AtomicDoubleArray[] costMatrix) { + this.dim = Math.max(costMatrix.length, costMatrix[0].length()); this.rows = costMatrix.length; - this.cols = costMatrix[0].length; - this.costMatrix = new double[this.dim][this.dim]; - for (int w = 0; w < this.dim; w++) { - if (w < costMatrix.length) { - if (costMatrix[w].length != this.cols) { - throw new IllegalArgumentException("Irregular cost matrix"); - } - for (int j = 0; j < this.cols; j++) { - if (Double.isInfinite(costMatrix[w][j])) { - throw new IllegalArgumentException("Infinite cost"); - } - if (Double.isNaN(costMatrix[w][j])) { - throw new IllegalArgumentException("NaN cost"); - } - } - this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim); - } else { - this.costMatrix[w] = new double[this.dim]; - } - } + this.cols = costMatrix[0].length(); + this.costMatrix = costMatrix; +// for (int w = 0; w < this.dim; w++) { +// if (w < costMatrix.length) { +// if (costMatrix[w].length() != this.cols) { +// throw new IllegalArgumentException("Irregular cost matrix"); +// } +//// for (int j = 0; j < this.cols; j++) { +//// if (Double.isInfinite(costMatrix[w].get(j))) { +//// throw new IllegalArgumentException("Infinite cost"); +//// } +//// if (Double.isNaN(costMatrix[w].get(j))) { +//// throw new IllegalArgumentException("NaN cost"); +//// } +//// } +// this.costMatrix[w] = costMatrix(costMatrix[w], this.dim); +// } else { +// this.costMatrix[w] = new double[this.dim]; +// } +// } labelByWorker = new double[this.dim]; labelByJob = new double[this.dim]; minSlackWorkerByJob = new int[this.dim]; @@ -127,8 +129,8 @@ protected void computeInitialFeasibleSolution() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < labelByJob[j]) { - labelByJob[j] = costMatrix[w][j]; + if (costMatrix[w].get(j) < labelByJob[j]) { + labelByJob[j] = costMatrix[w].get(j); } } } @@ -237,7 +239,7 @@ protected void executePhase() { committedWorkers[worker] = true; for (int j = 0; j < dim; j++) { if (parentWorkerByCommittedJob[j] == -1) { - double slack = costMatrix[worker][j] - labelByWorker[worker] + double slack = costMatrix[worker].get(j) - labelByWorker[worker] - labelByJob[j]; if (minSlackValueByJob[j] > slack) { minSlackValueByJob[j] = slack; @@ -270,7 +272,7 @@ protected void greedyMatch() { for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1 - && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) { + && costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j] == 0) { match(w, j); } } @@ -289,7 +291,7 @@ protected void initializePhase(int w) { Arrays.fill(parentWorkerByCommittedJob, -1); committedWorkers[w] = true; for (int j = 0; j < dim; j++) { - minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] - labelByJob[j]; + minSlackValueByJob[j] = costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j]; minSlackWorkerByJob[j] = w; } } @@ -312,12 +314,12 @@ protected void reduce() { for (int w = 0; w < dim; w++) { double min = Double.POSITIVE_INFINITY; for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < min) { - min = costMatrix[w][j]; + if (costMatrix[w].get(j) < min) { + min = costMatrix[w].get(j); } } for (int j = 0; j < dim; j++) { - costMatrix[w][j] -= min; + costMatrix[w].set(j, costMatrix[w].get(j) - min); } } double[] min = new double[dim]; @@ -326,14 +328,14 @@ protected void reduce() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < min[j]) { - min[j] = costMatrix[w][j]; + if (costMatrix[w].get(j) < min[j]) { + min[j] = costMatrix[w].get(j); } } } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - costMatrix[w][j] -= min[j]; + costMatrix[w].set(j, costMatrix[w].get(j) - min[j]); } } } @@ -361,7 +363,7 @@ protected void updateLabeling(double slack) { public int[] nextChild() { int currentJobAssigned = matchJobByWorker[0]; // we want to make currentJobAssigned not allowed,meaning we set the size to Infinity - costMatrix[0][currentJobAssigned] = Integer.MAX_VALUE; + costMatrix[0].set(currentJobAssigned, Integer.MAX_VALUE); matchWorkerByJob[currentJobAssigned] = -1; matchJobByWorker[0] = -1; minSlackValueByJob[currentJobAssigned] = Integer.MAX_VALUE; diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 926a170121..93fb72ae5a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -4,6 +4,7 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; +import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; import java.util.ArrayList; @@ -17,7 +18,11 @@ import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.stream.Collectors; import static graphql.Assert.assertTrue; @@ -62,19 +67,20 @@ public MappingEntry() { SchemaGraph sourceGraph; SchemaGraph targetGraph; + ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws InterruptedException { sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph); } - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws InterruptedException { return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws InterruptedException { if (sourceGraph.size() < targetGraph.size()) { sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); } else if (sourceGraph.size() > targetGraph.size()) { @@ -247,7 +253,7 @@ private void generateChildren(MappingEntry parentEntry, AtomicReference> bestEdit, SchemaGraph sourceGraph, SchemaGraph targetGraph - ) { + ) throws InterruptedException { Mapping partialMapping = parentEntry.partialMapping; assertTrue(level - 1 == partialMapping.size()); List sourceList = sourceGraph.getVertices(); @@ -262,54 +268,54 @@ private void generateChildren(MappingEntry parentEntry, int costMatrixSize = sourceList.size() - level + 1; - double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; +// double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); + // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them + AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); // we are skipping the first level -i indices Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); + + List> callables = new ArrayList<>(); + // costMatrix[0] is the row for v_i - int counter = 0; - int blockedCounter = 0; for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); - costMatrix[i - level + 1][j] = cost; - j++; - counter++; - if (cost == Integer.MAX_VALUE) { - blockedCounter++; - } else { -// System.out.println("not blocked " + v.getType()); + int finalI = i; + callables.add(() -> { + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); + costMatrix[finalI - level + 1].set(j, cost); + costMatrixCopy[finalI - level + 1].set(j, cost); + j++; } - } + return null; + }); } -// System.out.println("counter: " + counter + " vs " + blockedCounter + " perc: " + (blockedCounter / (double) counter)); + forkJoinPool.invokeAll(callables); + forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - - // generate all children (which are siblings to each other) List siblings = new ArrayList<>(); for (int child = 0; child < availableTargetVertices.size(); child++) { int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); - if (hungarianAlgorithm.costMatrix[0][assignments[0]] == Integer.MAX_VALUE) { + if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { break; } - double costMatrixSumSibling = getCostMatrixSum(costMatrix, assignments); + double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; -// System.out.println("lower bound: " + child + " : " + lowerBoundForPartialMappingSibling); int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); -// System.out.println("adding new mapping " + v_i + " => " + bestExtensionTargetVertexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMappingSibling == parentEntry.lowerBoundCost) { -// System.out.println("same lower Bound: " + v_i + " -> " + bestExtensionTargetVertexSibling); - } if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { break; @@ -319,7 +325,6 @@ private void generateChildren(MappingEntry parentEntry, sibling.assignments = assignments; sibling.availableTargetVertices = availableTargetVertices; - Set existingMappings = new LinkedHashSet<>(); // first child we add to the queue, otherwise save it for later if (child == 0) { // System.out.println("adding new child entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); @@ -383,10 +388,10 @@ private void getSibling( } - private double getCostMatrixSum(double[][] costMatrix, int[] assignments) { + private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { double costMatrixSum = 0; for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i][assignments[i]]; + costMatrixSum += costMatrix[i].get(assignments[i]); } return costMatrixSum; } From b07c5c3313f448b879978a29c00c3081ce0c56da Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 4 Jan 2022 16:31:35 +1100 Subject: [PATCH 033/294] performance --- .../graphql/schema/diffing/SchemaDiffing.java | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 93fb72ae5a..c4a139bfc1 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -282,7 +282,6 @@ private void generateChildren(MappingEntry parentEntry, List> callables = new ArrayList<>(); - // costMatrix[0] is the row for v_i for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); @@ -482,45 +481,24 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so return cost; } - static Map> allowedTypeMappings = new LinkedHashMap<>(); - - static { - allowedTypeMappings.put(DUMMY_TYPE_VERTEX, Collections.singletonList(DUMMY_TYPE_VERTEX)); - allowedTypeMappings.put(SCALAR, Collections.singletonList(SCALAR)); - allowedTypeMappings.put(ENUM, Collections.singletonList(ENUM)); - allowedTypeMappings.put(ENUM_VALUE, Collections.singletonList(ENUM_VALUE)); - allowedTypeMappings.put(OBJECT, Arrays.asList(OBJECT)); - allowedTypeMappings.put(INTERFACE, Arrays.asList(INTERFACE)); - allowedTypeMappings.put(FIELD, Collections.singletonList(FIELD)); - allowedTypeMappings.put(ARGUMENT, Collections.singletonList(ARGUMENT)); - allowedTypeMappings.put(INPUT_OBJECT, Collections.singletonList(INPUT_OBJECT)); - allowedTypeMappings.put(INPUT_FIELD, Collections.singletonList(INPUT_FIELD)); - allowedTypeMappings.put(UNION, Collections.singletonList(UNION)); - allowedTypeMappings.put(APPLIED_DIRECTIVE, Collections.singletonList(APPLIED_DIRECTIVE)); - allowedTypeMappings.put(APPLIED_ARGUMENT, Collections.singletonList(APPLIED_ARGUMENT)); - allowedTypeMappings.put(DIRECTIVE, Collections.singletonList(DIRECTIVE)); - } private Map forcedMatchingCache = new LinkedHashMap<>(); - private Map forcedMatchingNegativeCache = new LinkedHashMap<>(); private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet) { Vertex forcedMatch = forcedMatchingCache.get(v); if (forcedMatch != null) { return forcedMatch == u; } - // deletion and inserting of vertices + // deletion and inserting of vertices is possible if (u.isArtificialNode() || v.isArtificialNode()) { return true; } - List targetTypes = allowedTypeMappings.get(v.getType()); - if (targetTypes == null) { - return true; - } - boolean contains = targetTypes.contains(u.getType()); - if (!contains) { + // the types of the vertices need to match: we don't allow to change the type + if (!v.getType().equals(u.getType())) { return false; } + + // if it is named type we check if the targetGraph has one with the same name and type force a match if (isNamedType(v.getType())) { Vertex targetVertex = targetGraph.getType(v.get("name")); if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { @@ -553,7 +531,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S } } } - } if (INPUT_FIELD.equals(v.getType())) { Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); From 62bfaa988de638af9a4df0e0c1a6322882e5c4e2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 5 Jan 2022 20:43:10 +1100 Subject: [PATCH 034/294] handling of isolated vertices --- .../schema/diffing/HungarianAlgorithm.java | 74 +++--- .../graphql/schema/diffing/SchemaDiffing.java | 238 ++++++++++++------ .../graphql/schema/diffing/SchemaGraph.java | 24 +- .../schema/diffing/SchemaGraphFactory.java | 50 ++-- .../java/graphql/schema/diffing/Vertex.java | 18 +- .../schema/diffing/SchemaDiffingTest.groovy | 200 ++++++++------- 6 files changed, 377 insertions(+), 227 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index ed53a437cf..9c0ed5724c 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -1,7 +1,5 @@ package graphql.schema.diffing; -import com.google.common.util.concurrent.AtomicDoubleArray; - import java.util.Arrays; /* Copyright (c) 2012 Kevin L. Stern @@ -51,7 +49,7 @@ */ public class HungarianAlgorithm { // changed by reduce - public final AtomicDoubleArray[] costMatrix; + public final double[][] costMatrix; // constant always private final int rows; @@ -59,7 +57,7 @@ public class HungarianAlgorithm { private final int dim; // the assigned workers,jobs for the result - public final int[] matchJobByWorker; + private final int[] matchJobByWorker; private final int[] matchWorkerByJob; // reset for each execute @@ -83,29 +81,29 @@ public class HungarianAlgorithm { * irregular in the sense that all rows must be the same length; in * addition, all entries must be non-infinite numbers. */ - public HungarianAlgorithm(AtomicDoubleArray[] costMatrix) { - this.dim = Math.max(costMatrix.length, costMatrix[0].length()); + public HungarianAlgorithm(double[][] costMatrix) { + this.dim = Math.max(costMatrix.length, costMatrix[0].length); this.rows = costMatrix.length; - this.cols = costMatrix[0].length(); - this.costMatrix = costMatrix; -// for (int w = 0; w < this.dim; w++) { -// if (w < costMatrix.length) { -// if (costMatrix[w].length() != this.cols) { -// throw new IllegalArgumentException("Irregular cost matrix"); -// } -//// for (int j = 0; j < this.cols; j++) { -//// if (Double.isInfinite(costMatrix[w].get(j))) { -//// throw new IllegalArgumentException("Infinite cost"); -//// } -//// if (Double.isNaN(costMatrix[w].get(j))) { -//// throw new IllegalArgumentException("NaN cost"); -//// } -//// } -// this.costMatrix[w] = costMatrix(costMatrix[w], this.dim); -// } else { -// this.costMatrix[w] = new double[this.dim]; -// } -// } + this.cols = costMatrix[0].length; + this.costMatrix = new double[this.dim][this.dim]; + for (int w = 0; w < this.dim; w++) { + if (w < costMatrix.length) { + if (costMatrix[w].length != this.cols) { + throw new IllegalArgumentException("Irregular cost matrix"); + } + for (int j = 0; j < this.cols; j++) { + if (Double.isInfinite(costMatrix[w][j])) { + throw new IllegalArgumentException("Infinite cost"); + } + if (Double.isNaN(costMatrix[w][j])) { + throw new IllegalArgumentException("NaN cost"); + } + } + this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim); + } else { + this.costMatrix[w] = new double[this.dim]; + } + } labelByWorker = new double[this.dim]; labelByJob = new double[this.dim]; minSlackWorkerByJob = new int[this.dim]; @@ -129,8 +127,8 @@ protected void computeInitialFeasibleSolution() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w].get(j) < labelByJob[j]) { - labelByJob[j] = costMatrix[w].get(j); + if (costMatrix[w][j] < labelByJob[j]) { + labelByJob[j] = costMatrix[w][j]; } } } @@ -239,7 +237,7 @@ protected void executePhase() { committedWorkers[worker] = true; for (int j = 0; j < dim; j++) { if (parentWorkerByCommittedJob[j] == -1) { - double slack = costMatrix[worker].get(j) - labelByWorker[worker] + double slack = costMatrix[worker][j] - labelByWorker[worker] - labelByJob[j]; if (minSlackValueByJob[j] > slack) { minSlackValueByJob[j] = slack; @@ -272,7 +270,7 @@ protected void greedyMatch() { for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1 - && costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j] == 0) { + && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) { match(w, j); } } @@ -291,7 +289,7 @@ protected void initializePhase(int w) { Arrays.fill(parentWorkerByCommittedJob, -1); committedWorkers[w] = true; for (int j = 0; j < dim; j++) { - minSlackValueByJob[j] = costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j]; + minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] - labelByJob[j]; minSlackWorkerByJob[j] = w; } } @@ -314,12 +312,12 @@ protected void reduce() { for (int w = 0; w < dim; w++) { double min = Double.POSITIVE_INFINITY; for (int j = 0; j < dim; j++) { - if (costMatrix[w].get(j) < min) { - min = costMatrix[w].get(j); + if (costMatrix[w][j] < min) { + min = costMatrix[w][j]; } } for (int j = 0; j < dim; j++) { - costMatrix[w].set(j, costMatrix[w].get(j) - min); + costMatrix[w][j] -= min; } } double[] min = new double[dim]; @@ -328,14 +326,14 @@ protected void reduce() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w].get(j) < min[j]) { - min[j] = costMatrix[w].get(j); + if (costMatrix[w][j] < min[j]) { + min[j] = costMatrix[w][j]; } } } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - costMatrix[w].set(j, costMatrix[w].get(j) - min[j]); + costMatrix[w][j] -= min[j]; } } } @@ -363,7 +361,7 @@ protected void updateLabeling(double slack) { public int[] nextChild() { int currentJobAssigned = matchJobByWorker[0]; // we want to make currentJobAssigned not allowed,meaning we set the size to Infinity - costMatrix[0].set(currentJobAssigned, Integer.MAX_VALUE); + costMatrix[0][currentJobAssigned] = Integer.MAX_VALUE; matchWorkerByJob[currentJobAssigned] = -1; matchJobByWorker[0] = -1; minSlackValueByJob[currentJobAssigned] = Integer.MAX_VALUE; diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index c4a139bfc1..0aead2a427 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,15 +1,17 @@ package graphql.schema.diffing; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; -import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; +import graphql.util.FpKit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -20,15 +22,10 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.stream.Collectors; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; import static graphql.schema.diffing.SchemaGraphFactory.ENUM; @@ -41,6 +38,8 @@ import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; import static graphql.schema.diffing.SchemaGraphFactory.UNION; import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static java.util.Collections.synchronizedMap; public class SchemaDiffing { @@ -70,8 +69,8 @@ public MappingEntry() { ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws InterruptedException { - sourceGraph = new SchemaGraphFactory().createGraph(graphQLSchema1); - targetGraph = new SchemaGraphFactory().createGraph(graphQLSchema2); + sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); + targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph); } @@ -80,21 +79,67 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } + private void diffNamedList(Collection sourceVertices, + Collection targetVertices, + List deleted, // sourceVertices + List inserted, // targetVertices + BiMap same) { + Map sourceByName = FpKit.groupingByUniqueKey(sourceVertices, vertex -> vertex.get("name")); + Map targetByName = FpKit.groupingByUniqueKey(targetVertices, vertex -> vertex.get("name")); + for (Vertex sourceVertex : sourceVertices) { + Vertex targetVertex = targetByName.get((String) sourceVertex.get("name")); + if (targetVertex == null) { + deleted.add(sourceVertex); + } else { + same.put(sourceVertex, targetVertex); + } + } + + for (Vertex targetVertex : targetVertices) { + if (sourceByName.get((String) targetVertex.get("name")) == null) { + inserted.add(targetVertex); + } + } + } + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws InterruptedException { + int sizeDiff = sourceGraph.size() - targetGraph.size(); + System.out.println("graph diff: " + sizeDiff); + Map> isolatedSourceVertices = new LinkedHashMap<>(); + Map> isolatedTargetVertices = new LinkedHashMap<>(); + for (String type : SchemaGraphFactory.ALL_TYPES) { + Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); + Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); + if (sourceVertices.size() > targetVertices.size()) { + isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-")); + } else if (targetVertices.size() > sourceVertices.size()) { + isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-")); + } + } + for (Map.Entry> entry : isolatedSourceVertices.entrySet()) { + sourceGraph.addVertices(entry.getValue()); + } + for (Map.Entry> entry : isolatedTargetVertices.entrySet()) { + targetGraph.addVertices(entry.getValue()); + } + + Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); + Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); + // the only vertices left are built in types. if (sourceGraph.size() < targetGraph.size()) { - sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size()); + isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-artificial-")); } else if (sourceGraph.size() > targetGraph.size()) { - targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size()); + isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-")); } assertTrue(sourceGraph.size() == targetGraph.size()); + + // if (true) { +// return Collections.emptyList(); +// } int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); + // sortSourceGraph(sourceGraph, targetGraph); -// if (true) { -// String print = GraphPrinter.print(sourceGraph); -// System.out.println(print); -// return Collections.emptyList(); -// } AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); @@ -138,15 +183,23 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t bestFullMapping, bestEdit, sourceGraph, - targetGraph + targetGraph, + isolatedSourceVertices, + isolatedTargetVertices, + isolatedBuiltInSourceVertices, + isolatedBuiltInTargetVertices ); } } System.out.println("ged cost: " + upperBoundCost.doubleValue()); - System.out.println("edit : " + bestEdit); - for (EditOperation editOperation : bestEdit.get()) { - System.out.println(editOperation); - } +// List debugMap = getDebugMap(bestFullMapping.get()); +// for (String debugLine : debugMap) { +// System.out.println(debugLine); +// } +// System.out.println("edit : " + bestEdit); +// for (EditOperation editOperation : bestEdit.get()) { +// System.out.println(editOperation); +// } return bestEdit.get(); } @@ -186,7 +239,6 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { result.add(nextOne); nextCandidates.remove(nextOneIndex); } - System.out.println(result); sourceGraph.setVertices(result); } @@ -202,7 +254,8 @@ private List allAdjacentEdges(SchemaGraph schemaGraph, List fromLi return result; } - private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex vertex, List edges, Map vertexWeights, Map edgesWeights) { + private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex + vertex, List edges, Map vertexWeights, Map edgesWeights) { if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } @@ -212,7 +265,8 @@ private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex vertex, Lis return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); } - private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex vertex, Map vertexWeights, Map edgesWeights) { + private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex + vertex, Map vertexWeights, Map edgesWeights) { if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } @@ -249,10 +303,15 @@ private void generateChildren(MappingEntry parentEntry, int level, PriorityQueue queue, AtomicDouble upperBound, - AtomicReference bestMapping, + AtomicReference bestFullMapping, AtomicReference> bestEdit, SchemaGraph sourceGraph, - SchemaGraph targetGraph + SchemaGraph targetGraph, + Map> isolatedSourceVertices, + Map> isolatedTargetVertices, + Set isolatedBuiltInSourceVertices, + Set isolatedBuiltInTargetVertices + ) throws InterruptedException { Mapping partialMapping = parentEntry.partialMapping; assertTrue(level - 1 == partialMapping.size()); @@ -270,12 +329,12 @@ private void generateChildren(MappingEntry parentEntry, int costMatrixSize = sourceList.size() - level + 1; // double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; - Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); - // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them - AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; - Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); - + double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; +// Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); +// // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them +// AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; +// Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); +// // we are skipping the first level -i indices Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); @@ -286,30 +345,30 @@ private void generateChildren(MappingEntry parentEntry, for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int finalI = i; - callables.add(() -> { - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); - costMatrix[finalI - level + 1].set(j, cost); - costMatrixCopy[finalI - level + 1].set(j, cost); - j++; - } - return null; - }); +// callables.add(() -> { + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices); + costMatrix[finalI - level + 1][j] = cost; +// costMatrixCopy[finalI - level + 1].set(j, cost); + j++; + } +// return null; +// }); } - forkJoinPool.invokeAll(callables); - forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); +// forkJoinPool.invokeAll(callables); +// forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); List siblings = new ArrayList<>(); for (int child = 0; child < availableTargetVertices.size(); child++) { int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); - if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { + if (hungarianAlgorithm.costMatrix[0][assignments[0]] == Integer.MAX_VALUE) { break; } - double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); + double costMatrixSumSibling = getCostMatrixSum(costMatrix, assignments); double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); @@ -338,7 +397,7 @@ private void generateChildren(MappingEntry parentEntry, int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); if (costForFullMapping < upperBound.doubleValue()) { upperBound.set(costForFullMapping); - bestMapping.set(fullMapping); + bestFullMapping.set(fullMapping); bestEdit.set(editOperations); System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } @@ -387,15 +446,16 @@ private void getSibling( } - private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { + private double getCostMatrixSum(double[][] costMatrix, int[] assignments) { double costMatrixSum = 0; for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i].get(assignments[i]); + costMatrixSum += costMatrix[i][assignments[i]]; } return costMatrixSum; } - private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph + sourceGraph, SchemaGraph targetGraph) { String viType = v_i.getType(); HashMultiset viAdjacentEdges = HashMultiset.create(sourceGraph.getAdjacentEdges(v_i).stream().map(edge -> edge.getLabel()).collect(Collectors.toList())); if (viType.equals(SchemaGraphFactory.OBJECT) || viType.equals(INTERFACE) || viType.equals(UNION) || viType.equals(INPUT_OBJECT)) { @@ -428,7 +488,8 @@ private List getDebugMap(Mapping mapping) { } // minimum number of edit operations for a full mapping - private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph sourceGraph, SchemaGraph targetGraph, List editOperationsResult) { + private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph sourceGraph, SchemaGraph + targetGraph, List editOperationsResult) { int cost = 0; for (int i = 0; i < partialOrFullMapping.size(); i++) { Vertex sourceVertex = partialOrFullMapping.getSource(i); @@ -482,30 +543,36 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so } - private Map forcedMatchingCache = new LinkedHashMap<>(); + private Map forcedMatchingCache = synchronizedMap(new LinkedHashMap<>()); - private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet) { + private boolean isMappingPossible(Vertex v, + Vertex u, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + Set partialMappingTargetSet, + Map> isolatedSourceVertices, + Map> isolatedTargetVertices, + Set isolatedBuiltInSourceVertices, + Set isolatedBuiltInTargetVertices + ) { Vertex forcedMatch = forcedMatchingCache.get(v); if (forcedMatch != null) { return forcedMatch == u; } - // deletion and inserting of vertices is possible - if (u.isArtificialNode() || v.isArtificialNode()) { - return true; - } - // the types of the vertices need to match: we don't allow to change the type - if (!v.getType().equals(u.getType())) { + if (v.isArtificialNode() && u.isArtificialNode()) { return false; } - - // if it is named type we check if the targetGraph has one with the same name and type force a match if (isNamedType(v.getType())) { Vertex targetVertex = targetGraph.getType(v.get("name")); if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { - if (u == targetVertex) { - forcedMatchingCache.put(v, targetVertex); - forcedMatchingCache.put(targetVertex, v); - } + forcedMatchingCache.put(v, targetVertex); + return u == targetVertex; + } + } + if (DIRECTIVE.equals(v.getType())) { + Vertex targetVertex = targetGraph.getDirective(v.get("name")); + if (targetVertex != null) { + forcedMatchingCache.put(v, targetVertex); return u == targetVertex; } } @@ -518,7 +585,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S if (matchingTargetField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); - forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } else if (vertex.getType().equals(INPUT_FIELD)) { @@ -526,7 +592,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S if (matchingTargetInputField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); - forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } @@ -536,7 +601,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); if (matchingTargetInputField != null) { forcedMatchingCache.put(v, matchingTargetInputField); - forcedMatchingCache.put(matchingTargetInputField, v); return u == matchingTargetInputField; } } @@ -544,7 +608,6 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); if (matchingTargetField != null) { forcedMatchingCache.put(v, matchingTargetField); - forcedMatchingCache.put(matchingTargetField, v); return u == matchingTargetField; } } @@ -552,12 +615,30 @@ private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, S Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); if (matchingTargetEnumValue != null) { forcedMatchingCache.put(v, matchingTargetEnumValue); - forcedMatchingCache.put(matchingTargetEnumValue, v); return u == matchingTargetEnumValue; } } - return true; + if (v.isArtificialNode()) { + if (u.isBuiltInType()) { + return isolatedBuiltInSourceVertices.contains(v); + } else { + return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); + } + } + if (u.isArtificialNode()) { + if (v.isBuiltInType()) { + return isolatedBuiltInTargetVertices.contains(u); + } else { + return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); + } + } + // the types of the vertices need to match: we don't allow to change the type + if (!v.getType().equals(u.getType())) { + return false; + } + // if it is named type we check if the targetGraph has one with the same name and type force a match + return true; } private Vertex getDummyTypeVertex(Vertex vertex, SchemaGraph schemaGraph) { @@ -645,14 +726,19 @@ private double calcLowerBoundMappingCost(Vertex v, List partialMappingSourceList, Set partialMappingSourceSet, List partialMappingTargetList, - Set partialMappingTargetSet + Set partialMappingTargetSet, + Map> isolatedSourceVertices, + Map> isolatedTargetVertices, + Set isolatedBuiltInSourceVertices, + Set isolatedBuiltInTargetVertices + ) { - if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet)) { - return Integer.MAX_VALUE; - } - if (!isMappingPossible(u, v, targetGraph, sourceGraph, partialMappingSourceSet)) { + if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices)) { return Integer.MAX_VALUE; } +// if (!isMappingPossible(u, v, targetGraph, sourceGraph, partialMappingSourceSet)) { +// return Integer.MAX_VALUE; +// } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index cc560b13c0..e0dddc36df 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -2,9 +2,9 @@ import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashBiMap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; import com.google.common.collect.Table; -import graphql.collect.ImmutableKit; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -18,6 +18,7 @@ public class SchemaGraph { private Map typesByName = new LinkedHashMap<>(); private Map directivesByName = new LinkedHashMap<>(); private Table edgeByVertexPair = HashBasedTable.create(); + private Multimap typeToVertices = LinkedHashMultimap.create(); public SchemaGraph() { @@ -31,6 +32,17 @@ public SchemaGraph(List vertices, List edges, Table vertices) { + for(Vertex vertex: vertices) { + this.vertices.add(vertex); + typeToVertices.put(vertex.getType(), vertex); + } + } + + public Collection getVerticesByType(String type) { + return typeToVertices.get(type); } public void addEdge(Edge edge) { @@ -114,11 +126,13 @@ public int size() { return vertices.size(); } - public void addIsolatedVertices(int count) { - String uniqueType = String.valueOf(UUID.randomUUID()); + public List addIsolatedVertices(int count, String debugPrefix) { + List result = new ArrayList<>(); for (int i = 0; i < count; i++) { - Vertex isolatedVertex = Vertex.newArtificialNode(uniqueType); + Vertex isolatedVertex = Vertex.newArtificialNode(debugPrefix + i); vertices.add(isolatedVertex); + result.add(isolatedVertex); } + return result; } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index ab9a49a95f..445033a07c 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -10,6 +10,7 @@ import graphql.util.TraverserVisitor; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -18,6 +19,8 @@ public class SchemaGraphFactory { + public static final String ISOLATED = "__ISOLATED"; + public static final String DUMMY_TYPE_VERTEX = "__DUMMY_TYPE_VERTEX"; public static final String OBJECT = "Object"; public static final String INTERFACE = "Interface"; @@ -32,10 +35,20 @@ public class SchemaGraphFactory { public static final String APPLIED_ARGUMENT = "AppliedArgument"; public static final String DIRECTIVE = "Directive"; public static final String INPUT_FIELD = "InputField"; - public static final String VALUE = "Value"; - public static final String DIRECTIVE_LOCATION = "DirectiveLocation"; + + public static final List ALL_TYPES = Arrays.asList(DUMMY_TYPE_VERTEX, OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM, ENUM_VALUE, APPLIED_DIRECTIVE, FIELD, ARGUMENT, APPLIED_ARGUMENT, DIRECTIVE, INPUT_FIELD); + public static final List ALL_NAMED_TYPES = Arrays.asList(OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM); private int counter = 1; + private final String debugPrefix; + + public SchemaGraphFactory(String debugPrefix) { + this.debugPrefix = debugPrefix; + } + + public SchemaGraphFactory() { + this.debugPrefix = ""; + } public SchemaGraph createGraph(GraphQLSchema schema) { Set roots = new LinkedHashSet<>(); @@ -140,7 +153,7 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); @@ -196,7 +209,7 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); @@ -222,7 +235,7 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume } private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex objectVertex = new Vertex(OBJECT, String.valueOf(counter++)); + Vertex objectVertex = new Vertex(OBJECT, debugPrefix + String.valueOf(counter++)); objectVertex.setBuiltInType(isIntrospectionNode); objectVertex.add("name", graphQLObjectType.getName()); objectVertex.add("description", desc(graphQLObjectType.getDescription())); @@ -237,7 +250,7 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr } private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex fieldVertex = new Vertex(FIELD, String.valueOf(counter++)); + Vertex fieldVertex = new Vertex(FIELD, debugPrefix + String.valueOf(counter++)); fieldVertex.setBuiltInType(isIntrospectionNode); fieldVertex.add("name", graphQLFieldDefinition.getName()); fieldVertex.add("description", desc(graphQLFieldDefinition.getDescription())); @@ -251,7 +264,7 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra } private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex(ARGUMENT, String.valueOf(counter++)); + Vertex vertex = new Vertex(ARGUMENT, debugPrefix + String.valueOf(counter++)); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); vertex.add("description", desc(graphQLArgument.getDescription())); @@ -260,7 +273,7 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr } private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex scalarVertex = new Vertex(SCALAR, String.valueOf(counter++)); + Vertex scalarVertex = new Vertex(SCALAR, debugPrefix + String.valueOf(counter++)); scalarVertex.setBuiltInType(isIntrospectionNode); if (ScalarInfo.isGraphqlSpecifiedScalar(scalarType.getName())) { scalarVertex.setBuiltInType(true); @@ -274,7 +287,7 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, bo } private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex interfaceVertex = new Vertex(INTERFACE, String.valueOf(counter++)); + Vertex interfaceVertex = new Vertex(INTERFACE, debugPrefix + String.valueOf(counter++)); interfaceVertex.setBuiltInType(isIntrospectionNode); interfaceVertex.add("name", interfaceType.getName()); interfaceVertex.add("description", desc(interfaceType.getDescription())); @@ -289,12 +302,12 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema } private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex enumVertex = new Vertex(ENUM, String.valueOf(counter++)); + Vertex enumVertex = new Vertex(ENUM, debugPrefix + String.valueOf(counter++)); enumVertex.setBuiltInType(isIntrospectionNode); enumVertex.add("name", enumType.getName()); enumVertex.add("description", desc(enumType.getDescription())); for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { - Vertex enumValueVertex = new Vertex(ENUM_VALUE, String.valueOf(counter++)); + Vertex enumValueVertex = new Vertex(ENUM_VALUE, debugPrefix + String.valueOf(counter++)); enumValueVertex.setBuiltInType(isIntrospectionNode); enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); @@ -307,7 +320,7 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean } private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex unionVertex = new Vertex(UNION, String.valueOf(counter++)); + Vertex unionVertex = new Vertex(UNION, debugPrefix + String.valueOf(counter++)); unionVertex.setBuiltInType(isIntrospectionNode); unionVertex.add("name", unionType.getName()); unionVertex.add("description", desc(unionType.getDescription())); @@ -317,7 +330,7 @@ private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boole } private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex inputObjectVertex = new Vertex(INPUT_OBJECT, String.valueOf(counter++)); + Vertex inputObjectVertex = new Vertex(INPUT_OBJECT, debugPrefix + String.valueOf(counter++)); inputObjectVertex.setBuiltInType(isIntrospectionNode); inputObjectVertex.add("name", inputObject.getName()); inputObjectVertex.add("description", desc(inputObject.getDescription())); @@ -335,10 +348,10 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph schemaGraph) { for (GraphQLDirective appliedDirective : appliedDirectives) { - Vertex appliedDirectiveVertex = new Vertex(APPLIED_DIRECTIVE, String.valueOf(counter++)); + Vertex appliedDirectiveVertex = new Vertex(APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { - Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, String.valueOf(counter++)); + Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); appliedArgumentVertex.add("name", appliedArgument.getName()); appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); @@ -349,7 +362,7 @@ private void cratedAppliedDirectives(Vertex from, List applied } private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { - Vertex directiveVertex = new Vertex(DIRECTIVE, String.valueOf(counter++)); + Vertex directiveVertex = new Vertex(DIRECTIVE, debugPrefix + String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); directiveVertex.add("repeatable", directive.isRepeatable()); directiveVertex.add("locations", directive.validLocations()); @@ -366,7 +379,7 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { } private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex(INPUT_FIELD, String.valueOf(counter++)); + Vertex vertex = new Vertex(INPUT_FIELD, debugPrefix + String.valueOf(counter++)); schemaGraph.addVertex(vertex); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); @@ -376,7 +389,8 @@ private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph sch } private String desc(String desc) { - return desc != null ? desc.replace("\n", "\\n") : null; + return ""; +// return desc != null ? desc.replace("\n", "\\n") : null; } } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 44df56877d..af5e0c2513 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -1,8 +1,12 @@ package graphql.schema.diffing; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; public class Vertex { @@ -14,12 +18,22 @@ public class Vertex { private boolean builtInType; - public static Vertex newArtificialNode(String type) { - Vertex vertex = new Vertex(type, null); + public static Vertex newArtificialNode(String debugName) { + Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName); vertex.artificialNode = true; return vertex; } + public static Set newArtificialNodes(int count, String debugName) { + Set result = new LinkedHashSet<>(); + for (int i = 1; i <= count; i++) { + Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName + i); + vertex.artificialNode = true; + result.add(vertex); + } + return result; + } + public Vertex(String type, String debugName) { this.type = type; this.debugName = debugName; diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 2971a6c351..bae02a75be 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -11,6 +11,7 @@ import spock.lang.Ignore import spock.lang.Specification import static graphql.TestUtil.schema +import static graphql.TestUtil.schemaFromResource class SchemaDiffingTest extends Specification { @@ -173,7 +174,7 @@ class SchemaDiffingTest extends Specification { } - def "change Object into an Interface"() { + def "inserting interface with same name as previous object"() { given: def schema1 = schema(""" type Query { @@ -197,6 +198,9 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + for (EditOperation operation : diff) { + println operation + } then: /** @@ -206,6 +210,47 @@ class SchemaDiffingTest extends Specification { } + def "remove scalars and add Enums"() { + given: + def schema1 = schema(""" + scalar S1 + scalar S2 + scalar S3 + enum E1{ + E1 + } + type Query { + s1: S1 + s2: S2 + s3: S3 + e1: E1 + } + """) + def schema2 = schema(""" + enum E1{ + E1 + } + enum E2{ + E2 + } + type Query { + e1: E1 + e2: E2 + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + for (EditOperation operation : diff) { + println operation + } + + then: + diff.size() == 19 + + } + + @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) @@ -326,7 +371,9 @@ class SchemaDiffingTest extends Specification { def diffing = new SchemaDiffing() when: def diff = diffing.diffGraphQLSchema(schema1, schema2) - new DefaultGraphEditOperationAnalyzer(schema1, schema2, diffing.sourceGraph, diffing.targetGraph, changeHandler).analyzeEdits(diff) + for(EditOperation editOperation: diff) { + println editOperation + } then: diff.size() == 5 @@ -757,91 +804,68 @@ class SchemaDiffingTest extends Specification { operations.size() == 2 } + def "with directives"() { + given: + def schema1 = schema(''' + directive @TopLevelType on OBJECT + directive @specialId(type: String) on ARGUMENT_DEFINITION + type Query { + foo: Foo + } + type Foo @TopLevelType { + user(location: ID! @specialId(type : "someId"), limit: Int = 25, start: Int, title: String): PaginatedList + } + type PaginatedList { + count: Int + } + ''') + def schema2 = schema(""" + directive @TopLevelType on OBJECT + directive @specialId(type: String) on ARGUMENT_DEFINITION + type Query { + foo: Foo + } + type Foo @TopLevelType { + user(location: ID! @specialId(type : "someId"), limit: Int = 25, start: Int, title: String): PaginatedList + other(after: String, favourite: Boolean, first: Int = 25, location: ID! @specialId(type : "someId"), label: [String], offset: Int, status: String, type: String): PaginatedList + } + type PaginatedList { + count: Int + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 32 + } + + def "built in directives"() { + given: + def schema1 = schema(''' + directive @specialId(type: String) on FIELD_DEFINITION + + + type Query { + hello: String @specialId(type: "someId") + } + ''') + def schema2 = schema(""" + directive @specialId(type: String) on FIELD_DEFINITION + + type Query { + renamedHello: String @specialId(type: "otherId") + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + + then: + operations.size() == 1 -// def "test example schema"() { -// given: -// def source = buildSourceGraph() -// def target = buildTargetGraph() -// when: -// new SchemaDiffing().diffImpl(source, target, oldVersion) -// then: -// true -// } -// -// def "test example schema 2"() { -// given: -// def source = sourceGraph2() -// def target = targetGraph2() -// when: -// new SchemaDiffing().diffImpl(source, target, oldVersion) -// then: -// true -// } -// -// SchemaGraph sourceGraph2() { -// def source = new SchemaGraph() -// Vertex a = new Vertex("A") -// source.addVertex(a) -// Vertex b = new Vertex("B") -// source.addVertex(b) -// Vertex c = new Vertex("C") -// source.addVertex(c) -// Vertex d = new Vertex("D") -// source.addVertex(d) -// source.addEdge(new Edge(a, b)) -// source.addEdge(new Edge(b, c)) -// source.addEdge(new Edge(c, d)) -// source -// } -// -// SchemaGraph targetGraph2() { -// def target = new SchemaGraph() -// Vertex a = new Vertex("A") -// Vertex d = new Vertex("D") -// target.addVertex(a) -// target.addVertex(d) -// target -// } -// -// SchemaGraph buildTargetGraph() { -// SchemaGraph targetGraph = new SchemaGraph(); -// def a_1 = new Vertex("A", "u1") -// def d = new Vertex("D", "u2") -// def a_2 = new Vertex("A", "u3") -// def a_3 = new Vertex("A", "u4") -// def e = new Vertex("E", "u5") -// targetGraph.addVertex(a_1); -// targetGraph.addVertex(d); -// targetGraph.addVertex(a_2); -// targetGraph.addVertex(a_3); -// targetGraph.addVertex(e); -// -// targetGraph.addEdge(new Edge(a_1, d, "a")) -// targetGraph.addEdge(new Edge(d, a_2, "a")) -// targetGraph.addEdge(new Edge(a_2, a_3, "a")) -// targetGraph.addEdge(new Edge(a_3, e, "a")) -// targetGraph -// -// } -// -// SchemaGraph buildSourceGraph() { -// SchemaGraph sourceGraph = new SchemaGraph(); -// def c = new Vertex("C", "v5") -// def a_1 = new Vertex("A", "v1") -// def a_2 = new Vertex("A", "v2") -// def a_3 = new Vertex("A", "v3") -// def b = new Vertex("B", "v4") -// sourceGraph.addVertex(a_1); -// sourceGraph.addVertex(a_2); -// sourceGraph.addVertex(a_3); -// sourceGraph.addVertex(b); -// sourceGraph.addVertex(c); -// -// sourceGraph.addEdge(new Edge(c, a_1, "b")) -// sourceGraph.addEdge(new Edge(a_1, a_2, "a")) -// sourceGraph.addEdge(new Edge(a_2, a_3, "a")) -// sourceGraph.addEdge(new Edge(a_3, b, "a")) -// sourceGraph -// -// } + } } + + From 48ce479a99669078090bfe0ecd5b66441916f5a5 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Jan 2022 09:52:27 +1100 Subject: [PATCH 035/294] most parallel version --- .../schema/diffing/HungarianAlgorithm.java | 74 ++--- .../graphql/schema/diffing/SchemaDiffing.java | 275 +++++++++++------- 2 files changed, 213 insertions(+), 136 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index 9c0ed5724c..ed53a437cf 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import com.google.common.util.concurrent.AtomicDoubleArray; + import java.util.Arrays; /* Copyright (c) 2012 Kevin L. Stern @@ -49,7 +51,7 @@ */ public class HungarianAlgorithm { // changed by reduce - public final double[][] costMatrix; + public final AtomicDoubleArray[] costMatrix; // constant always private final int rows; @@ -57,7 +59,7 @@ public class HungarianAlgorithm { private final int dim; // the assigned workers,jobs for the result - private final int[] matchJobByWorker; + public final int[] matchJobByWorker; private final int[] matchWorkerByJob; // reset for each execute @@ -81,29 +83,29 @@ public class HungarianAlgorithm { * irregular in the sense that all rows must be the same length; in * addition, all entries must be non-infinite numbers. */ - public HungarianAlgorithm(double[][] costMatrix) { - this.dim = Math.max(costMatrix.length, costMatrix[0].length); + public HungarianAlgorithm(AtomicDoubleArray[] costMatrix) { + this.dim = Math.max(costMatrix.length, costMatrix[0].length()); this.rows = costMatrix.length; - this.cols = costMatrix[0].length; - this.costMatrix = new double[this.dim][this.dim]; - for (int w = 0; w < this.dim; w++) { - if (w < costMatrix.length) { - if (costMatrix[w].length != this.cols) { - throw new IllegalArgumentException("Irregular cost matrix"); - } - for (int j = 0; j < this.cols; j++) { - if (Double.isInfinite(costMatrix[w][j])) { - throw new IllegalArgumentException("Infinite cost"); - } - if (Double.isNaN(costMatrix[w][j])) { - throw new IllegalArgumentException("NaN cost"); - } - } - this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim); - } else { - this.costMatrix[w] = new double[this.dim]; - } - } + this.cols = costMatrix[0].length(); + this.costMatrix = costMatrix; +// for (int w = 0; w < this.dim; w++) { +// if (w < costMatrix.length) { +// if (costMatrix[w].length() != this.cols) { +// throw new IllegalArgumentException("Irregular cost matrix"); +// } +//// for (int j = 0; j < this.cols; j++) { +//// if (Double.isInfinite(costMatrix[w].get(j))) { +//// throw new IllegalArgumentException("Infinite cost"); +//// } +//// if (Double.isNaN(costMatrix[w].get(j))) { +//// throw new IllegalArgumentException("NaN cost"); +//// } +//// } +// this.costMatrix[w] = costMatrix(costMatrix[w], this.dim); +// } else { +// this.costMatrix[w] = new double[this.dim]; +// } +// } labelByWorker = new double[this.dim]; labelByJob = new double[this.dim]; minSlackWorkerByJob = new int[this.dim]; @@ -127,8 +129,8 @@ protected void computeInitialFeasibleSolution() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < labelByJob[j]) { - labelByJob[j] = costMatrix[w][j]; + if (costMatrix[w].get(j) < labelByJob[j]) { + labelByJob[j] = costMatrix[w].get(j); } } } @@ -237,7 +239,7 @@ protected void executePhase() { committedWorkers[worker] = true; for (int j = 0; j < dim; j++) { if (parentWorkerByCommittedJob[j] == -1) { - double slack = costMatrix[worker][j] - labelByWorker[worker] + double slack = costMatrix[worker].get(j) - labelByWorker[worker] - labelByJob[j]; if (minSlackValueByJob[j] > slack) { minSlackValueByJob[j] = slack; @@ -270,7 +272,7 @@ protected void greedyMatch() { for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { if (matchJobByWorker[w] == -1 && matchWorkerByJob[j] == -1 - && costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) { + && costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j] == 0) { match(w, j); } } @@ -289,7 +291,7 @@ protected void initializePhase(int w) { Arrays.fill(parentWorkerByCommittedJob, -1); committedWorkers[w] = true; for (int j = 0; j < dim; j++) { - minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w] - labelByJob[j]; + minSlackValueByJob[j] = costMatrix[w].get(j) - labelByWorker[w] - labelByJob[j]; minSlackWorkerByJob[j] = w; } } @@ -312,12 +314,12 @@ protected void reduce() { for (int w = 0; w < dim; w++) { double min = Double.POSITIVE_INFINITY; for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < min) { - min = costMatrix[w][j]; + if (costMatrix[w].get(j) < min) { + min = costMatrix[w].get(j); } } for (int j = 0; j < dim; j++) { - costMatrix[w][j] -= min; + costMatrix[w].set(j, costMatrix[w].get(j) - min); } } double[] min = new double[dim]; @@ -326,14 +328,14 @@ protected void reduce() { } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - if (costMatrix[w][j] < min[j]) { - min[j] = costMatrix[w][j]; + if (costMatrix[w].get(j) < min[j]) { + min[j] = costMatrix[w].get(j); } } } for (int w = 0; w < dim; w++) { for (int j = 0; j < dim; j++) { - costMatrix[w][j] -= min[j]; + costMatrix[w].set(j, costMatrix[w].get(j) - min[j]); } } } @@ -361,7 +363,7 @@ protected void updateLabeling(double slack) { public int[] nextChild() { int currentJobAssigned = matchJobByWorker[0]; // we want to make currentJobAssigned not allowed,meaning we set the size to Infinity - costMatrix[0][currentJobAssigned] = Integer.MAX_VALUE; + costMatrix[0].set(currentJobAssigned, Integer.MAX_VALUE); matchWorkerByJob[currentJobAssigned] = -1; matchJobByWorker[0] = -1; minSlackValueByJob[currentJobAssigned] = Integer.MAX_VALUE; diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 0aead2a427..fb34a1d428 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -6,6 +6,7 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; +import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; import graphql.util.FpKit; @@ -20,9 +21,20 @@ import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import static graphql.Assert.assertTrue; @@ -43,11 +55,13 @@ public class SchemaDiffing { - private static class MappingEntry { + private static MappingEntry LAST_ELEMENT = new MappingEntry(); - public List mappingEntriesSiblings = new ArrayList<>(); + private static class MappingEntry { + public boolean siblingsFinished; + public LinkedBlockingQueue mappingEntriesSiblings; public int[] assignments; - public ArrayList availableTargetVertices; + public List availableTargetVertices; public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost) { this.partialMapping = partialMapping; @@ -67,6 +81,7 @@ public MappingEntry() { SchemaGraph sourceGraph; SchemaGraph targetGraph; ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); + ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws InterruptedException { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); @@ -111,9 +126,16 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); if (sourceVertices.size() > targetVertices.size()) { - isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-")); + isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); } else if (targetVertices.size() > sourceVertices.size()) { - isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-")); + isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-" + type + "-")); + } + if (isNamedType(type)) { + ArrayList deleted = new ArrayList<>(); + ArrayList inserted = new ArrayList<>(); + HashBiMap same = HashBiMap.create(); + diffNamedList(sourceVertices, targetVertices, deleted, inserted, same); + System.out.println(" " + type + " deleted " + deleted.size() + " inserted" + inserted.size() + " same " + same.size()); } } for (Map.Entry> entry : isolatedSourceVertices.entrySet()) { @@ -127,19 +149,17 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); // the only vertices left are built in types. if (sourceGraph.size() < targetGraph.size()) { - isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-artificial-")); + isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-artificial-builtin-")); } else if (sourceGraph.size() > targetGraph.size()) { - isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-")); + isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-builtin-")); } assertTrue(sourceGraph.size() == targetGraph.size()); + IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices); - // if (true) { -// return Collections.emptyList(); -// } int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); -// sortSourceGraph(sourceGraph, targetGraph); + sortSourceGraph(sourceGraph, targetGraph); AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); @@ -157,14 +177,14 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); -// System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); - if ((++counter) % 100 == 0) { - System.out.println((counter) + " entry at level"); - } +// System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); +// if ((++counter) % 100 == 0) { +// System.out.println((counter) + " entry at level"); +// } if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { continue; } - if (mappingEntry.level > 0 && mappingEntry.mappingEntriesSiblings.size() > 0) { + if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { getSibling( mappingEntry.level, queue, @@ -184,10 +204,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t bestEdit, sourceGraph, targetGraph, - isolatedSourceVertices, - isolatedTargetVertices, - isolatedBuiltInSourceVertices, - isolatedBuiltInTargetVertices + isolatedInfo ); } } @@ -307,10 +324,7 @@ private void generateChildren(MappingEntry parentEntry, AtomicReference> bestEdit, SchemaGraph sourceGraph, SchemaGraph targetGraph, - Map> isolatedSourceVertices, - Map> isolatedTargetVertices, - Set isolatedBuiltInSourceVertices, - Set isolatedBuiltInTargetVertices + IsolatedInfo isolatedInfo ) throws InterruptedException { Mapping partialMapping = parentEntry.partialMapping; @@ -329,11 +343,11 @@ private void generateChildren(MappingEntry parentEntry, int costMatrixSize = sourceList.size() - level + 1; // double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; -// Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); -// // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them -// AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; -// Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); + AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); + // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them + AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); // // we are skipping the first level -i indices Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); @@ -345,37 +359,100 @@ private void generateChildren(MappingEntry parentEntry, for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int finalI = i; -// callables.add(() -> { - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices); - costMatrix[finalI - level + 1][j] = cost; -// costMatrixCopy[finalI - level + 1].set(j, cost); - j++; - } -// return null; -// }); + callables.add(() -> { + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); + costMatrix[finalI - level + 1].set(j, cost); + costMatrixCopy[finalI - level + 1].set(j, cost); + j++; + } + return null; + }); } -// forkJoinPool.invokeAll(callables); -// forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); + forkJoinPool.invokeAll(callables); + forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - List siblings = new ArrayList<>(); - for (int child = 0; child < availableTargetVertices.size(); child++) { - int[] assignments = child == 0 ? hungarianAlgorithm.execute() : hungarianAlgorithm.nextChild(); - if (hungarianAlgorithm.costMatrix[0][assignments[0]] == Integer.MAX_VALUE) { + + int[] assignments = hungarianAlgorithm.execute(); + + double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); + double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; + int v_i_target_IndexSibling = assignments[0]; + Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); + Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); + + + if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { + return; + } + MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); + LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); +// newMappingEntry.siblingsReady = new AtomicBoolean(); + newMappingEntry.mappingEntriesSiblings = siblings; + newMappingEntry.assignments = assignments; + newMappingEntry.availableTargetVertices = availableTargetVertices; + + queue.add(newMappingEntry); + Mapping fullMapping = partialMapping.copy(); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + } + + assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestFullMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + } + + executorService.submit(new Runnable() { + @Override + public void run() { + calculateChildren( + availableTargetVertices, + hungarianAlgorithm, + costMatrixCopy, + editorialCostForMapping, + partialMapping, + v_i, + upperBound.get(), + level, + siblings + ); + } + }); + } + + private void calculateChildren(List availableTargetVertices, + HungarianAlgorithm hungarianAlgorithm, + AtomicDoubleArray[] costMatrixCopy, + double editorialCostForMapping, + Mapping partialMapping, + Vertex v_i, + double upperBound, + int level, + LinkedBlockingQueue siblings + ) { + for (int child = 1; child < availableTargetVertices.size(); child++) { + int[] assignments = hungarianAlgorithm.nextChild(); + if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { break; } - double costMatrixSumSibling = getCostMatrixSum(costMatrix, assignments); + double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { + if (lowerBoundForPartialMappingSibling >= upperBound) { break; } MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); @@ -384,28 +461,10 @@ private void generateChildren(MappingEntry parentEntry, sibling.availableTargetVertices = availableTargetVertices; // first child we add to the queue, otherwise save it for later - if (child == 0) { -// System.out.println("adding new child entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); - queue.add(sibling); - Mapping fullMapping = partialMapping.copy(); - for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); - } - - assertTrue(fullMapping.size() == sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestFullMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } - } else { - siblings.add(sibling); - } - +// System.out.println("add child " + child); + siblings.add(sibling); } + siblings.add(LAST_ELEMENT); } @@ -417,14 +476,17 @@ private void getSibling( AtomicReference> bestEdit, SchemaGraph sourceGraph, SchemaGraph targetGraph, - MappingEntry mappingEntry) { + MappingEntry mappingEntry) throws InterruptedException { - MappingEntry sibling = mappingEntry.mappingEntriesSiblings.get(0); + MappingEntry sibling = mappingEntry.mappingEntriesSiblings.take(); + if (sibling == LAST_ELEMENT) { + mappingEntry.siblingsFinished = true; + return; + } if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { // System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); - mappingEntry.mappingEntriesSiblings.remove(0); List sourceList = sourceGraph.getVertices(); // we need to start here from the parent mapping, this is why we remove the last element @@ -446,10 +508,10 @@ private void getSibling( } - private double getCostMatrixSum(double[][] costMatrix, int[] assignments) { + private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { double costMatrixSum = 0; for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i][assignments[i]]; + costMatrixSum += costMatrix[i].get(assignments[i]); } return costMatrixSum; } @@ -545,16 +607,28 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so private Map forcedMatchingCache = synchronizedMap(new LinkedHashMap<>()); + static class IsolatedInfo { + Map> isolatedSourceVertices; + Map> isolatedTargetVertices; + Set isolatedBuiltInSourceVertices; + Set isolatedBuiltInTargetVertices; + + public IsolatedInfo(Map> isolatedSourceVertices, Map> isolatedTargetVertices, Set isolatedBuiltInSourceVertices, Set isolatedBuiltInTargetVertices) { + this.isolatedSourceVertices = isolatedSourceVertices; + this.isolatedTargetVertices = isolatedTargetVertices; + this.isolatedBuiltInSourceVertices = isolatedBuiltInSourceVertices; + this.isolatedBuiltInTargetVertices = isolatedBuiltInTargetVertices; + } + } + private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet, - Map> isolatedSourceVertices, - Map> isolatedTargetVertices, - Set isolatedBuiltInSourceVertices, - Set isolatedBuiltInTargetVertices + IsolatedInfo isolatedInfo ) { + Vertex forcedMatch = forcedMatchingCache.get(v); if (forcedMatch != null) { return forcedMatch == u; @@ -562,6 +636,28 @@ private boolean isMappingPossible(Vertex v, if (v.isArtificialNode() && u.isArtificialNode()) { return false; } + Map> isolatedSourceVertices = isolatedInfo.isolatedSourceVertices; + Map> isolatedTargetVertices = isolatedInfo.isolatedTargetVertices; + Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; + Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; + if (v.isArtificialNode()) { + if (u.isBuiltInType()) { + return isolatedBuiltInSourceVertices.contains(v); + } else { + return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); + } + } + if (u.isArtificialNode()) { + if (v.isBuiltInType()) { + return isolatedBuiltInTargetVertices.contains(u); + } else { + return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); + } + } + // the types of the vertices need to match: we don't allow to change the type + if (!v.getType().equals(u.getType())) { + return false; + } if (isNamedType(v.getType())) { Vertex targetVertex = targetGraph.getType(v.get("name")); if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { @@ -618,24 +714,6 @@ private boolean isMappingPossible(Vertex v, return u == matchingTargetEnumValue; } } - if (v.isArtificialNode()) { - if (u.isBuiltInType()) { - return isolatedBuiltInSourceVertices.contains(v); - } else { - return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); - } - } - if (u.isArtificialNode()) { - if (v.isBuiltInType()) { - return isolatedBuiltInTargetVertices.contains(u); - } else { - return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); - } - } - // the types of the vertices need to match: we don't allow to change the type - if (!v.getType().equals(u.getType())) { - return false; - } // if it is named type we check if the targetGraph has one with the same name and type force a match return true; @@ -727,13 +805,10 @@ private double calcLowerBoundMappingCost(Vertex v, Set partialMappingSourceSet, List partialMappingTargetList, Set partialMappingTargetSet, - Map> isolatedSourceVertices, - Map> isolatedTargetVertices, - Set isolatedBuiltInSourceVertices, - Set isolatedBuiltInTargetVertices + IsolatedInfo isolatedInfo ) { - if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices)) { + if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedInfo)) { return Integer.MAX_VALUE; } // if (!isMappingPossible(u, v, targetGraph, sourceGraph, partialMappingSourceSet)) { From fee3be34638e4e76261e9e250f251f9327ef8ea8 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Jan 2022 11:04:01 +1100 Subject: [PATCH 036/294] performance --- .../graphql/schema/diffing/SchemaDiffing.java | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index fb34a1d428..8cca60014a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -640,6 +640,7 @@ private boolean isMappingPossible(Vertex v, Map> isolatedTargetVertices = isolatedInfo.isolatedTargetVertices; Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; + if (v.isArtificialNode()) { if (u.isBuiltInType()) { return isolatedBuiltInSourceVertices.contains(v); @@ -658,17 +659,32 @@ private boolean isMappingPossible(Vertex v, if (!v.getType().equals(u.getType())) { return false; } - if (isNamedType(v.getType())) { - Vertex targetVertex = targetGraph.getType(v.get("name")); - if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { - forcedMatchingCache.put(v, targetVertex); - return u == targetVertex; - } + Boolean result = checkNamedTypes(v, u, targetGraph); + if (result != null) { + return result; } + result = checkNamedTypes(u, v, sourceGraph); + if (result != null) { + return result; + } + result = checkSpecificTypes(v, u, sourceGraph, targetGraph); + if (result != null) { + return result; + } + result = checkSpecificTypes(u, v, targetGraph, sourceGraph); + if (result != null) { + return result; + } + + return true; + } + + private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph) { if (DIRECTIVE.equals(v.getType())) { Vertex targetVertex = targetGraph.getDirective(v.get("name")); if (targetVertex != null) { forcedMatchingCache.put(v, targetVertex); + forcedMatchingCache.put(targetVertex, v); return u == targetVertex; } } @@ -681,6 +697,7 @@ private boolean isMappingPossible(Vertex v, if (matchingTargetField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); + forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } else if (vertex.getType().equals(INPUT_FIELD)) { @@ -688,6 +705,7 @@ private boolean isMappingPossible(Vertex v, if (matchingTargetInputField != null) { Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); forcedMatchingCache.put(v, dummyTypeVertex); + forcedMatchingCache.put(dummyTypeVertex, v); return u == dummyTypeVertex; } } @@ -697,6 +715,7 @@ private boolean isMappingPossible(Vertex v, Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); if (matchingTargetInputField != null) { forcedMatchingCache.put(v, matchingTargetInputField); + forcedMatchingCache.put(matchingTargetInputField, v); return u == matchingTargetInputField; } } @@ -704,6 +723,7 @@ private boolean isMappingPossible(Vertex v, Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); if (matchingTargetField != null) { forcedMatchingCache.put(v, matchingTargetField); + forcedMatchingCache.put(matchingTargetField, v); return u == matchingTargetField; } } @@ -711,12 +731,23 @@ private boolean isMappingPossible(Vertex v, Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); if (matchingTargetEnumValue != null) { forcedMatchingCache.put(v, matchingTargetEnumValue); + forcedMatchingCache.put(matchingTargetEnumValue, v); return u == matchingTargetEnumValue; } } + return null; + } - // if it is named type we check if the targetGraph has one with the same name and type force a match - return true; + private Boolean checkNamedTypes(Vertex v, Vertex u, SchemaGraph targetGraph) { + if (isNamedType(v.getType())) { + Vertex targetVertex = targetGraph.getType(v.get("name")); + if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { + forcedMatchingCache.put(v, targetVertex); + forcedMatchingCache.put(targetVertex, v); + return u == targetVertex; + } + } + return null; } private Vertex getDummyTypeVertex(Vertex vertex, SchemaGraph schemaGraph) { @@ -811,13 +842,10 @@ private double calcLowerBoundMappingCost(Vertex v, if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedInfo)) { return Integer.MAX_VALUE; } -// if (!isMappingPossible(u, v, targetGraph, sourceGraph, partialMappingSourceSet)) { -// return Integer.MAX_VALUE; -// } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); + // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges - List adjacentEdgesV = sourceGraph.getAdjacentEdges(v); Multiset multisetLabelsV = HashMultiset.create(); From 410cb49e84ea60c822eece74ca4a9cb6e41c3436 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Jan 2022 16:21:08 +1100 Subject: [PATCH 037/294] handle fields better --- .../graphql/schema/diffing/SchemaDiffing.java | 156 +++++++++++++++--- src/main/java/graphql/util/FpKit.java | 11 ++ .../schema/diffing/SchemaDiffingTest.groovy | 116 ++++++++++++- 3 files changed, 260 insertions(+), 23 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 8cca60014a..1109ececa0 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,10 +1,13 @@ package graphql.schema.diffing; import com.google.common.collect.BiMap; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; +import com.google.common.collect.Table; import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; @@ -21,21 +24,15 @@ import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.Stream; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; @@ -117,33 +114,123 @@ private void diffNamedList(Collection sourceVertices, } } + private void diffNamedList(Set sourceNames, + Set targetNames, + List deleted, + List inserted, + List same) { + for (String sourceName : sourceNames) { + if (targetNames.contains(sourceName)) { + same.add(sourceName); + } else { + deleted.add(sourceName); + } + } + + for (String targetName : targetNames) { + if (!sourceNames.contains(targetName)) { + inserted.add(targetName); + } + } + } + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws InterruptedException { int sizeDiff = sourceGraph.size() - targetGraph.size(); System.out.println("graph diff: " + sizeDiff); Map> isolatedSourceVertices = new LinkedHashMap<>(); Map> isolatedTargetVertices = new LinkedHashMap<>(); + Table> isolatedTargetVerticesForFields = HashBasedTable.create(); + Table> isolatedSourceVerticesForFields = HashBasedTable.create(); for (String type : SchemaGraphFactory.ALL_TYPES) { - Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); - Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); - if (sourceVertices.size() > targetVertices.size()) { - isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); - } else if (targetVertices.size() > sourceVertices.size()) { - isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-" + type + "-")); - } - if (isNamedType(type)) { - ArrayList deleted = new ArrayList<>(); - ArrayList inserted = new ArrayList<>(); - HashBiMap same = HashBiMap.create(); - diffNamedList(sourceVertices, targetVertices, deleted, inserted, same); - System.out.println(" " + type + " deleted " + deleted.size() + " inserted" + inserted.size() + " same " + same.size()); + if (FIELD.equals(type)) { + + Stream sourceVerticesStream = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); + Stream targetVerticesStream = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); + Map> sourceFieldsByContainer = FpKit.groupingBy(sourceVerticesStream, v -> { + Vertex fieldsContainer = getFieldsContainer(v, sourceGraph); + return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); + }); + Map> targetFieldsByContainer = FpKit.groupingBy(targetVerticesStream, v -> { + Vertex fieldsContainer = getFieldsContainer(v, targetGraph); + return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); + }); + + List deletedContainers = new ArrayList<>(); + List insertedContainers = new ArrayList<>(); + List sameContainers = new ArrayList<>(); + diffNamedList(sourceFieldsByContainer.keySet(), targetFieldsByContainer.keySet(), deletedContainers, insertedContainers, sameContainers); + + for (String sameContainer : sameContainers) { + int ix = sameContainer.indexOf("-"); + String containerName = sameContainer.substring(ix + 1); + // we have this container is source and target + ImmutableList sourceVertices = sourceFieldsByContainer.get(sameContainer); + ImmutableList targetVertices = targetFieldsByContainer.get(sameContainer); + + ArrayList deletedFields = new ArrayList<>(); + ArrayList insertedFields = new ArrayList<>(); + HashBiMap sameFields = HashBiMap.create(); + diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); + + if (deletedFields.size() > insertedFields.size()) { + Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + type + "-"); + for (Vertex deletedField : deletedFields) { + isolatedTargetVerticesForFields.put(containerName, deletedField.get("name"), newTargetVertices); + } + } else if (insertedFields.size() > deletedFields.size()) { + Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + type + "-"); + for (Vertex insertedField : insertedFields) { + isolatedSourceVerticesForFields.put(containerName, insertedField.get("name"), newSourceVertices); + } + } + } + + Set insertedFields = new LinkedHashSet<>(); + Set deletedFields = new LinkedHashSet<>(); + for (String insertedContainer : insertedContainers) { + insertedFields.addAll(targetFieldsByContainer.get(insertedContainer)); + } + for (String deletedContainer : deletedContainers) { + deletedFields.addAll(sourceFieldsByContainer.get(deletedContainer)); + } + if (deletedFields.size() > insertedFields.size()) { + isolatedTargetVertices.put(type, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-FIELD-")); + } else if (insertedFields.size() > deletedFields.size()) { + isolatedSourceVertices.put(type, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-FIELD-")); + } + + } else { + Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); + Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); + if (sourceVertices.size() > targetVertices.size()) { + isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); + } else if (targetVertices.size() > sourceVertices.size()) { + isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-" + type + "-")); + } } +// if (isNamedType(type)) { +// ArrayList deleted = new ArrayList<>(); +// ArrayList inserted = new ArrayList<>(); +// HashBiMap same = HashBiMap.create(); +// diffNamedList(sourceVertices, targetVertices, deleted, inserted, same); +// System.out.println(" " + type + " deleted " + deleted.size() + " inserted" + inserted.size() + " same " + same.size()); +// } } + for (Map.Entry> entry : isolatedSourceVertices.entrySet()) { sourceGraph.addVertices(entry.getValue()); } for (Map.Entry> entry : isolatedTargetVertices.entrySet()) { targetGraph.addVertices(entry.getValue()); } + for (String containerName : isolatedSourceVerticesForFields.rowKeySet()) { + Map> row = isolatedSourceVerticesForFields.row(containerName); + sourceGraph.addVertices(row.values().iterator().next()); + } + for (String containerName : isolatedTargetVerticesForFields.rowKeySet()) { + Map> row = isolatedTargetVerticesForFields.row(containerName); + targetGraph.addVertices(row.values().iterator().next()); + } Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); @@ -154,7 +241,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-builtin-")); } assertTrue(sourceGraph.size() == targetGraph.size()); - IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices); + IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices, isolatedSourceVerticesForFields, isolatedTargetVerticesForFields); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); @@ -612,12 +699,19 @@ static class IsolatedInfo { Map> isolatedTargetVertices; Set isolatedBuiltInSourceVertices; Set isolatedBuiltInTargetVertices; + Table> isolatedSourceVerticesForFields; + Table> isolatedTargetVerticesForFields; - public IsolatedInfo(Map> isolatedSourceVertices, Map> isolatedTargetVertices, Set isolatedBuiltInSourceVertices, Set isolatedBuiltInTargetVertices) { + public IsolatedInfo(Map> isolatedSourceVertices, Map> isolatedTargetVertices, Set isolatedBuiltInSourceVertices, + Set isolatedBuiltInTargetVertices, + Table> isolatedSourceVerticesForFields, + Table> isolatedTargetVerticesForFields) { this.isolatedSourceVertices = isolatedSourceVertices; this.isolatedTargetVertices = isolatedTargetVertices; this.isolatedBuiltInSourceVertices = isolatedBuiltInSourceVertices; this.isolatedBuiltInTargetVertices = isolatedBuiltInTargetVertices; + this.isolatedSourceVerticesForFields = isolatedSourceVerticesForFields; + this.isolatedTargetVerticesForFields = isolatedTargetVerticesForFields; } } @@ -640,11 +734,21 @@ private boolean isMappingPossible(Vertex v, Map> isolatedTargetVertices = isolatedInfo.isolatedTargetVertices; Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; + Table> isolatedSourceVerticesForFields = isolatedInfo.isolatedSourceVerticesForFields; + Table> isolatedTargetVerticesForFields = isolatedInfo.isolatedTargetVerticesForFields; if (v.isArtificialNode()) { if (u.isBuiltInType()) { return isolatedBuiltInSourceVertices.contains(v); } else { + if (u.getType().equals(FIELD)) { + Vertex fieldsContainer = getFieldsContainer(u, targetGraph); + String containerName = fieldsContainer.get("name"); + String fieldName = u.get("name"); + if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { + return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); + } + } return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); } } @@ -652,6 +756,14 @@ private boolean isMappingPossible(Vertex v, if (v.isBuiltInType()) { return isolatedBuiltInTargetVertices.contains(u); } else { + if (v.getType().equals(FIELD)) { + Vertex fieldsContainer = getFieldsContainer(v, sourceGraph); + String containerName = fieldsContainer.get("name"); + String fieldName = u.get("name"); + if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { + return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); + } + } return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); } } diff --git a/src/main/java/graphql/util/FpKit.java b/src/main/java/graphql/util/FpKit.java index 8e52c118f5..b84831617c 100644 --- a/src/main/java/graphql/util/FpKit.java +++ b/src/main/java/graphql/util/FpKit.java @@ -48,6 +48,9 @@ public static Map> groupingBy(Collection return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); } + public static Map> groupingBy(Stream stream, Function function) { + return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); + } public static Map groupingByUniqueKey(Collection list, Function keyFunction) { return list.stream().collect(Collectors.toMap( keyFunction, @@ -56,6 +59,14 @@ public static Map groupingByUniqueKey(Collection list, LinkedHashMap::new) ); } + public static Map groupingByUniqueKey(Stream stream, Function keyFunction) { + return stream.collect(Collectors.toMap( + keyFunction, + identity(), + throwingMerger(), + LinkedHashMap::new) + ); + } private static BinaryOperator throwingMerger() { return (u, v) -> { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index bae02a75be..c051fce387 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -53,6 +53,120 @@ class SchemaDiffingTest extends Specification { } + def "adding fields and rename and delete"() { + given: + def schema1 = schema(""" + type Query { + hello: String + toDelete:String + newField: String + newField2: String + } + type Mutation { + unchanged: Boolean + unchanged2: Other + } + type Other { + id: ID + } + """) + def schema2 = schema(""" + type Query { + helloRenamed: String + newField: String + newField2: String + } + type Mutation { + unchanged: Boolean + unchanged2: Other + } + type Other { + id: ID + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + diff.each { println it } + then: + diff.size() == 6 + + } + + def "remove field and rename type"() { + given: + def schema1 = schema(""" + type Query { + foo: Foo + } + type Foo { + bar: Bar + toDelete:String + } + type Bar { + id: ID + name: String + } + """) + def schema2 = schema(""" + type Query { + foo: FooRenamed + } + type FooRenamed { + bar: Bar + } + type Bar { + id: ID + name: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + diff.each { println it } + then: + diff.size() == 7 + + } + + def "renamed field and added field and type"() { + given: + def schema1 = schema(""" + type Query { + foo: Foo + } + type Foo { + foo:String + } + """) + def schema2 = schema(""" + type Query { + foo: Foo + } + type Foo { + fooRenamed:String + bar: Bar + } + type Bar { + id: String + name: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + diff.each { println it } + then: + /** + * 1: Changed Field + * 2: New Object + * 3-8: Three new Fields + DummyTypes + * 9-17: Edges from Object to new Fields (3) + Edges from Field to Dummy Type (3) + Edges from DummyType to String + * */ + diff.size() == 17 + + } + def "test two field renames one type rename"() { given: @@ -371,7 +485,7 @@ class SchemaDiffingTest extends Specification { def diffing = new SchemaDiffing() when: def diff = diffing.diffGraphQLSchema(schema1, schema2) - for(EditOperation editOperation: diff) { + for (EditOperation editOperation : diff) { println editOperation } From 06803c160944cc7cc70119c1e7d795e248b0e79a Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Jan 2022 18:36:44 +1100 Subject: [PATCH 038/294] improve isMatchingPossible --- .../graphql/schema/diffing/SchemaDiffing.java | 241 ++++++++++++++---- .../schema/diffing/SchemaGraphFactory.java | 19 +- .../schema/diffing/SchemaDiffingTest.groovy | 10 +- 3 files changed, 216 insertions(+), 54 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1109ececa0..688403fb7f 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -10,6 +10,7 @@ import com.google.common.collect.Table; import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; +import graphql.Assert; import graphql.schema.GraphQLSchema; import graphql.util.FpKit; @@ -35,6 +36,9 @@ import java.util.stream.Stream; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; +import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; import static graphql.schema.diffing.SchemaGraphFactory.ENUM; @@ -147,11 +151,11 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t Stream sourceVerticesStream = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); Stream targetVerticesStream = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); Map> sourceFieldsByContainer = FpKit.groupingBy(sourceVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainer(v, sourceGraph); + Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); }); Map> targetFieldsByContainer = FpKit.groupingBy(targetVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainer(v, targetGraph); + Vertex fieldsContainer = getFieldsContainerForField(v, targetGraph); return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); }); @@ -202,6 +206,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t } else { Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); + System.out.println("type: " + type + " source size: " + sourceVertices.size()); if (sourceVertices.size() > targetVertices.size()) { isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); } else if (targetVertices.size() > sourceVertices.size()) { @@ -447,14 +452,19 @@ private void generateChildren(MappingEntry parentEntry, Vertex v = sourceList.get(i); int finalI = i; callables.add(() -> { - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); - costMatrix[finalI - level + 1].set(j, cost); - costMatrixCopy[finalI - level + 1].set(j, cost); - j++; + try { + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); + costMatrix[finalI - level + 1].set(j, cost); + costMatrixCopy[finalI - level + 1].set(j, cost); + j++; + } + return null; + } catch (Throwable t) { + t.printStackTrace(); + return null; } - return null; }); } forkJoinPool.invokeAll(callables); @@ -742,7 +752,7 @@ private boolean isMappingPossible(Vertex v, return isolatedBuiltInSourceVertices.contains(v); } else { if (u.getType().equals(FIELD)) { - Vertex fieldsContainer = getFieldsContainer(u, targetGraph); + Vertex fieldsContainer = getFieldsContainerForField(u, targetGraph); String containerName = fieldsContainer.get("name"); String fieldName = u.get("name"); if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { @@ -757,7 +767,7 @@ private boolean isMappingPossible(Vertex v, return isolatedBuiltInTargetVertices.contains(u); } else { if (v.getType().equals(FIELD)) { - Vertex fieldsContainer = getFieldsContainer(v, sourceGraph); + Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); String containerName = fieldsContainer.get("name"); String fieldName = u.get("name"); if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { @@ -792,13 +802,96 @@ private boolean isMappingPossible(Vertex v, } private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex matchingTargetVertex = findMatchingTargetVertex(v, sourceGraph, targetGraph); + if (matchingTargetVertex != null) { + forcedMatchingCache.put(v, matchingTargetVertex); + forcedMatchingCache.put(matchingTargetVertex, v); + return u == matchingTargetVertex; + } + return null; + +// if (DIRECTIVE.equals(v.getType())) { +// Vertex targetVertex = targetGraph.getDirective(v.get("name")); +// if (targetVertex != null) { +// forcedMatchingCache.put(v, targetVertex); +// forcedMatchingCache.put(targetVertex, v); +// return u == targetVertex; +// } +// } +// +// if (DUMMY_TYPE_VERTEX.equals(v.getType())) { +// List adjacentVertices = sourceGraph.getAdjacentVertices(v); +// for (Vertex vertex : adjacentVertices) { +// if (vertex.getType().equals(FIELD)) { +// Vertex matchingTargetField = findMatchingTargetField(vertex, sourceGraph, targetGraph); +// if (matchingTargetField != null) { +// Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); +// forcedMatchingCache.put(v, dummyTypeVertex); +// forcedMatchingCache.put(dummyTypeVertex, v); +// return u == dummyTypeVertex; +// } +// } else if (vertex.getType().equals(INPUT_FIELD)) { +// Vertex matchingTargetInputField = findMatchingTargetInputField(vertex, sourceGraph, targetGraph); +// if (matchingTargetInputField != null) { +// Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); +// forcedMatchingCache.put(v, dummyTypeVertex); +// forcedMatchingCache.put(dummyTypeVertex, v); +// return u == dummyTypeVertex; +// } +// } +// } +// } +// if (INPUT_FIELD.equals(v.getType())) { +// Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); +// if (matchingTargetInputField != null) { +// forcedMatchingCache.put(v, matchingTargetInputField); +// forcedMatchingCache.put(matchingTargetInputField, v); +// return u == matchingTargetInputField; +// } +// } +// if (FIELD.equals(v.getType())) { +// Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); +// if (matchingTargetField != null) { +// forcedMatchingCache.put(v, matchingTargetField); +// forcedMatchingCache.put(matchingTargetField, v); +// return u == matchingTargetField; +// } +// } +// if (ENUM_VALUE.equals(v.getType())) { +// Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); +// if (matchingTargetEnumValue != null) { +// forcedMatchingCache.put(v, matchingTargetEnumValue); +// forcedMatchingCache.put(matchingTargetEnumValue, v); +// return u == matchingTargetEnumValue; +// } +// } +// if (ARGUMENT.equals(v.getType())) { +// Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); +// if (matchingTargetArgument != null) { +// forcedMatchingCache.put(v, matchingTargetArgument); +// forcedMatchingCache.put(matchingTargetArgument, v); +// return u == matchingTargetArgument; +// } +// } +// if (APPLIED_ARGUMENT.equals(v.getType())) { +// Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); +// if (matchingTargetArgument != null) { +// forcedMatchingCache.put(v, matchingTargetArgument); +// forcedMatchingCache.put(matchingTargetArgument, v); +// return u == matchingTargetArgument; +// } +// } +// return null; + } + + private Vertex findMatchingTargetVertex(Vertex v, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + if (isNamedType(v.getType())) { + Vertex targetVertex = targetGraph.getType(v.get("name")); + // the type of the target vertex must match v + return targetVertex != null && targetVertex.getType().equals(v.getType()) ? targetVertex : null; + } if (DIRECTIVE.equals(v.getType())) { - Vertex targetVertex = targetGraph.getDirective(v.get("name")); - if (targetVertex != null) { - forcedMatchingCache.put(v, targetVertex); - forcedMatchingCache.put(targetVertex, v); - return u == targetVertex; - } + return targetGraph.getDirective(v.get("name")); } if (DUMMY_TYPE_VERTEX.equals(v.getType())) { @@ -807,47 +900,42 @@ private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, if (vertex.getType().equals(FIELD)) { Vertex matchingTargetField = findMatchingTargetField(vertex, sourceGraph, targetGraph); if (matchingTargetField != null) { - Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); - forcedMatchingCache.put(v, dummyTypeVertex); - forcedMatchingCache.put(dummyTypeVertex, v); - return u == dummyTypeVertex; + return getDummyTypeVertex(matchingTargetField, targetGraph); } } else if (vertex.getType().equals(INPUT_FIELD)) { Vertex matchingTargetInputField = findMatchingTargetInputField(vertex, sourceGraph, targetGraph); if (matchingTargetInputField != null) { - Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); - forcedMatchingCache.put(v, dummyTypeVertex); - forcedMatchingCache.put(dummyTypeVertex, v); - return u == dummyTypeVertex; + return getDummyTypeVertex(matchingTargetInputField, targetGraph); } } } + return null; } if (INPUT_FIELD.equals(v.getType())) { Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); - if (matchingTargetInputField != null) { - forcedMatchingCache.put(v, matchingTargetInputField); - forcedMatchingCache.put(matchingTargetInputField, v); - return u == matchingTargetInputField; - } + return matchingTargetInputField; } if (FIELD.equals(v.getType())) { Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); - if (matchingTargetField != null) { - forcedMatchingCache.put(v, matchingTargetField); - forcedMatchingCache.put(matchingTargetField, v); - return u == matchingTargetField; - } + return matchingTargetField; } if (ENUM_VALUE.equals(v.getType())) { Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); - if (matchingTargetEnumValue != null) { - forcedMatchingCache.put(v, matchingTargetEnumValue); - forcedMatchingCache.put(matchingTargetEnumValue, v); - return u == matchingTargetEnumValue; - } + return matchingTargetEnumValue; } - return null; + if (ARGUMENT.equals(v.getType())) { + Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); + return matchingTargetArgument; + } + + if (APPLIED_DIRECTIVE.equals(v.getType())) { + return findMatchingAppliedDirective(v, sourceGraph, targetGraph); + } + if (APPLIED_ARGUMENT.equals(v.getType())) { + return findMatchingAppliedArgument(v, sourceGraph, targetGraph); + } + + return Assert.assertShouldNeverHappen(); } private Boolean checkNamedTypes(Vertex v, Vertex u, SchemaGraph targetGraph) { @@ -889,12 +977,60 @@ private Vertex findMatchingTargetInputField(Vertex inputField, SchemaGraph sourc } + private Vertex findMatchingTargetArgument(Vertex argument, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex fieldOrDirective = getFieldOrDirectiveForArgument(argument, sourceGraph); + if (FIELD.equals(fieldOrDirective.getType())) { + Vertex matchingTargetField = findMatchingTargetField(fieldOrDirective, sourceGraph, targetGraph); + if (matchingTargetField != null) { + return getArgumentForFieldOrDirective(matchingTargetField, argument.get("name"), targetGraph); + } + } else { + // we have an Argument for a Directive + Vertex matchingDirective = targetGraph.getDirective(fieldOrDirective.get("name")); + if (matchingDirective != null) { + return getArgumentForFieldOrDirective(matchingDirective, argument.get("name"), targetGraph); + } + } + return null; + } + + private Vertex findMatchingAppliedDirective(Vertex appliedDirective, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + Vertex targetDirectiveWithSameName = targetGraph.getDirective(appliedDirective.get("name")); + if (targetDirectiveWithSameName == null) { + return null; + } + List adjacentVertices = sourceGraph.getAdjacentVertices(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); + assertTrue(adjacentVertices.size() == 1); + Vertex elementDirectiveAppliedTo = adjacentVertices.get(0); + //TODO: handle repeatable directives + Vertex targetMatchingAppliedTo = findMatchingTargetVertex(elementDirectiveAppliedTo, sourceGraph, targetGraph); + if (targetMatchingAppliedTo == null) { + return null; + } + List targetAppliedDirectives = targetGraph.getAdjacentVertices(targetMatchingAppliedTo, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE) && vertex.get("name").equals(appliedDirective.get("name"))); + return targetAppliedDirectives.size() == 0 ? null : targetAppliedDirectives.get(0); + } + + private Vertex findMatchingAppliedArgument(Vertex appliedArgument, SchemaGraph sourceGraph, SchemaGraph targetGraph) { + List appliedDirectives = sourceGraph.getAdjacentVertices(appliedArgument, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE)); + assertTrue(appliedDirectives.size() == 1); + Vertex appliedDirective = appliedDirectives.get(0); + Vertex matchingAppliedDirective = findMatchingAppliedDirective(appliedDirective, sourceGraph, targetGraph); + if (matchingAppliedDirective == null) { + return null; + } + List matchingAppliedArguments = targetGraph.getAdjacentVertices(matchingAppliedDirective, vertex -> vertex.getType().equals(APPLIED_ARGUMENT) && vertex.get("name").equals(appliedArgument.get("name"))); + assertTrue(matchingAppliedArguments.size() <= 1); + return matchingAppliedArguments.size() == 0 ? null : matchingAppliedArguments.get(0); + + } + private Vertex findMatchingTargetField(Vertex field, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex sourceFieldsContainer = getFieldsContainer(field, sourceGraph); + Vertex sourceFieldsContainer = getFieldsContainerForField(field, sourceGraph); Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { - Vertex mathchingField = getField(targetFieldsContainerWithSameName, field.get("name"), targetGraph); - return mathchingField; + Vertex matchingField = getFieldForContainer(targetFieldsContainerWithSameName, field.get("name"), targetGraph); + return matchingField; } return null; } @@ -905,7 +1041,7 @@ private Vertex getInputField(Vertex inputObject, String fieldName, SchemaGraph s return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); } - private Vertex getField(Vertex fieldsContainer, String fieldName, SchemaGraph schemaGraph) { + private Vertex getFieldForContainer(Vertex fieldsContainer, String fieldName, SchemaGraph schemaGraph) { List adjacentVertices = schemaGraph.getAdjacentVertices(fieldsContainer, v -> v.getType().equals(FIELD) && fieldName.equals(v.get("name"))); assertTrue(adjacentVertices.size() <= 1); return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); @@ -923,7 +1059,20 @@ private Vertex getInputFieldsObject(Vertex inputField, SchemaGraph schemaGraph) return adjacentVertices.get(0); } - private Vertex getFieldsContainer(Vertex field, SchemaGraph schemaGraph) { + private Vertex getFieldOrDirectiveForArgument(Vertex argument, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(argument, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(DIRECTIVE)); + assertTrue(adjacentVertices.size() == 1, () -> format("No field or directive found for %s", argument)); + return adjacentVertices.get(0); + } + + private Vertex getArgumentForFieldOrDirective(Vertex fieldOrDirective, String argumentName, SchemaGraph schemaGraph) { + assertTrue(DIRECTIVE.equals(fieldOrDirective.getType()) || FIELD.equals(fieldOrDirective.getType())); + List adjacentVertices = schemaGraph.getAdjacentVertices(fieldOrDirective, vertex -> vertex.getType().equals(ARGUMENT) && vertex.get("name").equals(argumentName)); + assertTrue(adjacentVertices.size() <= 1); + return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); + } + + private Vertex getFieldsContainerForField(Vertex field, SchemaGraph schemaGraph) { List adjacentVertices = schemaGraph.getAdjacentVertices(field, vertex -> vertex.getType().equals(OBJECT) || vertex.getType().equals(INTERFACE)); assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", field)); return adjacentVertices.get(0); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 445033a07c..d4e8410e8d 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -1,6 +1,8 @@ package graphql.schema.diffing; +import graphql.execution.ValuesResolver; import graphql.introspection.Introspection; +import graphql.language.AstPrinter; import graphql.schema.*; import graphql.schema.idl.DirectiveInfo; import graphql.schema.idl.ScalarInfo; @@ -135,8 +137,8 @@ public TraversalControl leave(TraverserContext context) { } private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { - Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); - schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); +// Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); +// schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); } private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { @@ -268,6 +270,9 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); vertex.add("description", desc(graphQLArgument.getDescription())); + if (graphQLArgument.hasSetDefaultValue()) { + vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType()))); + } cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); return vertex; } @@ -353,8 +358,11 @@ private void cratedAppliedDirectives(Vertex from, List applied for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); appliedArgumentVertex.add("name", appliedArgument.getName()); - appliedArgumentVertex.add("value", appliedArgument.getArgumentValue()); - schemaGraph.addEdge(new Edge(appliedArgumentVertex, appliedArgumentVertex)); + if (appliedArgument.hasSetValue()) { + appliedArgumentVertex.add("value", AstPrinter.printAst(ValuesResolver.valueToLiteral(appliedArgument.getArgumentValue(), appliedArgument.getType()))); + } + schemaGraph.addVertex(appliedArgumentVertex); + schemaGraph.addEdge(new Edge(appliedDirectiveVertex, appliedArgumentVertex)); } schemaGraph.addVertex(appliedDirectiveVertex); schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); @@ -384,6 +392,9 @@ private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph sch vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); vertex.add("description", desc(inputField.getDescription())); + if (inputField.hasSetDefaultValue()) { + vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType()))); + } cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); return vertex; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index c051fce387..18099d4292 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -47,7 +47,7 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) - println diff + diff.each { println it } then: diff.size() == 1 @@ -542,9 +542,10 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) + diff.each {println it} then: - diff.size() == 58 + diff.size() == 59 } @@ -952,7 +953,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 32 + operations.size() == 33 } def "built in directives"() { @@ -975,9 +976,10 @@ class SchemaDiffingTest extends Specification { when: def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } then: - operations.size() == 1 + operations.size() == 2 } } From f82d2f1db1185005886bb6be9052573aa7c1dbaa Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Jan 2022 21:54:48 +1100 Subject: [PATCH 039/294] bugfixing --- .../graphql/schema/diffing/SchemaDiffing.java | 68 +++++++++++-------- .../schema/diffing/SchemaGraphFactory.java | 1 - .../schema/diffing/SchemaDiffingTest.groovy | 42 ++++++++++++ 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 688403fb7f..fcdd2290ce 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -31,6 +31,7 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -84,14 +85,14 @@ public MappingEntry() { ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws InterruptedException { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph); } - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws InterruptedException { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } @@ -138,7 +139,7 @@ private void diffNamedList(Set sourceNames, } } - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws InterruptedException { + List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = sourceGraph.size() - targetGraph.size(); System.out.println("graph diff: " + sizeDiff); Map> isolatedSourceVertices = new LinkedHashMap<>(); @@ -206,7 +207,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t } else { Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); - System.out.println("type: " + type + " source size: " + sourceVertices.size()); +// System.out.println("type: " + type + " source size: " + sourceVertices.size()); if (sourceVertices.size() > targetVertices.size()) { isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); } else if (targetVertices.size() > sourceVertices.size()) { @@ -418,7 +419,7 @@ private void generateChildren(MappingEntry parentEntry, SchemaGraph targetGraph, IsolatedInfo isolatedInfo - ) throws InterruptedException { + ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; assertTrue(level - 1 == partialMapping.size()); List sourceList = sourceGraph.getVertices(); @@ -451,13 +452,16 @@ private void generateChildren(MappingEntry parentEntry, for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int finalI = i; - callables.add(() -> { + Callable callable = () -> { try { int j = 0; for (Vertex u : availableTargetVertices) { double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); costMatrix[finalI - level + 1].set(j, cost); costMatrixCopy[finalI - level + 1].set(j, cost); +// if (v.getType().equals("AppliedArgument") && ((String) v.get("value")).contains("jira-software") && cost != Integer.MAX_VALUE) { +// System.out.println((finalI - level + 1) + ", " + j + ": cost: " + cost + " for " + v + " to " + u); +// } j++; } return null; @@ -465,18 +469,21 @@ private void generateChildren(MappingEntry parentEntry, t.printStackTrace(); return null; } - }); + }; + callables.add(callable); +// callable.call(); } forkJoinPool.invokeAll(callables); forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); - int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); int[] assignments = hungarianAlgorithm.execute(); + int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); + double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); + - double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); - double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; + double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSum; int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); @@ -613,24 +620,24 @@ private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignment return costMatrixSum; } - private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph - sourceGraph, SchemaGraph targetGraph) { - String viType = v_i.getType(); - HashMultiset viAdjacentEdges = HashMultiset.create(sourceGraph.getAdjacentEdges(v_i).stream().map(edge -> edge.getLabel()).collect(Collectors.toList())); - if (viType.equals(SchemaGraphFactory.OBJECT) || viType.equals(INTERFACE) || viType.equals(UNION) || viType.equals(INPUT_OBJECT)) { - for (Vertex targetVertex : availableTargetVertices) { - if (v_i.isEqualTo(targetVertex)) { - // check if edges are the same - HashMultiset adjacentEdges = HashMultiset.create(targetGraph.getAdjacentEdges(targetVertex).stream().map(Edge::getLabel).collect(Collectors.toList())); - if (viAdjacentEdges.equals(adjacentEdges)) { - return targetVertex; - } - } - - } - } - return null; - } +// private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph +// sourceGraph, SchemaGraph targetGraph) { +// String viType = v_i.getType(); +// HashMultiset viAdjacentEdges = HashMultiset.create(sourceGraph.getAdjacentEdges(v_i).stream().map(edge -> edge.getLabel()).collect(Collectors.toList())); +// if (viType.equals(SchemaGraphFactory.OBJECT) || viType.equals(INTERFACE) || viType.equals(UNION) || viType.equals(INPUT_OBJECT)) { +// for (Vertex targetVertex : availableTargetVertices) { +// if (v_i.isEqualTo(targetVertex)) { +// // check if edges are the same +// HashMultiset adjacentEdges = HashMultiset.create(targetGraph.getAdjacentEdges(targetVertex).stream().map(Edge::getLabel).collect(Collectors.toList())); +// if (viAdjacentEdges.equals(adjacentEdges)) { +// return targetVertex; +// } +// } +// +// } +// } +// return null; +// } private List getDebugMap(Mapping mapping) { List result = new ArrayList<>(); @@ -1145,7 +1152,10 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - return (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; + double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; + return result; } + AtomicInteger zeroResult = new AtomicInteger(); + } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index d4e8410e8d..6efe73e796 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -343,7 +343,6 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche Vertex newInputField = newInputField(inputObjectField, schemaGraph, isIntrospectionNode); Edge newEdge = new Edge(inputObjectVertex, newInputField); schemaGraph.addEdge(newEdge); - cratedAppliedDirectives(inputObjectVertex, inputObjectField.getDirectives(), schemaGraph); } schemaGraph.addVertex(inputObjectVertex); schemaGraph.addType(inputObject.getName(), inputObjectVertex); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 18099d4292..4339e359d0 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -980,7 +980,49 @@ class SchemaDiffingTest extends Specification { then: operations.size() == 2 + } + + def "unchanged scheme"() { + given: + def schema1 = schema(''' + directive @specialId(type: String) on FIELD_DEFINITION + directive @Magic(owner: String!, type: String!) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + + + type Query { + hello: String @specialId(type: "someId") + foo(arg: Int, arg2: String = "hello"): [Foo]! + old: Boolean @deprecated + someOther(input1: MyInput, input2: OtherInput): E + } + type Foo { + id: ID + e1: E + union: MyUnion + } + union MyUnion = Foo | Bar + type Bar { + id: ID + } + enum E { + E1, E2, E3 + } + input MyInput { + id: ID + other: String! @Magic(owner: "Me", type: "SomeType") + } + input OtherInput { + inputField1: ID! @Magic(owner: "O1", type: "T1") + inputField2: ID! @Magic(owner: "O2", type: "T2") + } + ''') + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema1) + operations.each { println it } + + then: + operations.size() == 0 } } From 16c7496d29f02b5b52232468bc2cbb64be2917dc Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Jan 2022 23:09:52 +1100 Subject: [PATCH 040/294] only sort if diff size is not zero --- src/main/java/graphql/schema/diffing/SchemaDiffing.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index fcdd2290ce..5c2fc83f4e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -140,7 +140,7 @@ private void diffNamedList(Set sourceNames, } List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { - int sizeDiff = sourceGraph.size() - targetGraph.size(); + int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); Map> isolatedSourceVertices = new LinkedHashMap<>(); Map> isolatedTargetVertices = new LinkedHashMap<>(); @@ -252,7 +252,9 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); - sortSourceGraph(sourceGraph, targetGraph); + if (sizeDiff != 0) { + sortSourceGraph(sourceGraph, targetGraph); + } AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); AtomicReference bestFullMapping = new AtomicReference<>(); From 0264bcfc420695370ac3845779637b5e648180c9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Jan 2022 12:03:19 +1100 Subject: [PATCH 041/294] handle input fields correctly --- .../graphql/schema/diffing/SchemaDiffing.java | 260 +++++++++++++----- .../schema/diffing/SchemaDiffingTest.groovy | 47 +++- 2 files changed, 236 insertions(+), 71 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 5c2fc83f4e..1607c38f2c 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -146,81 +146,32 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t Map> isolatedTargetVertices = new LinkedHashMap<>(); Table> isolatedTargetVerticesForFields = HashBasedTable.create(); Table> isolatedSourceVerticesForFields = HashBasedTable.create(); + Table> isolatedTargetVerticesForInputFields = HashBasedTable.create(); + Table> isolatedSourceVerticesForInputFields = HashBasedTable.create(); for (String type : SchemaGraphFactory.ALL_TYPES) { if (FIELD.equals(type)) { - - Stream sourceVerticesStream = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); - Stream targetVerticesStream = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()); - Map> sourceFieldsByContainer = FpKit.groupingBy(sourceVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); - return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); - }); - Map> targetFieldsByContainer = FpKit.groupingBy(targetVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainerForField(v, targetGraph); - return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); - }); - - List deletedContainers = new ArrayList<>(); - List insertedContainers = new ArrayList<>(); - List sameContainers = new ArrayList<>(); - diffNamedList(sourceFieldsByContainer.keySet(), targetFieldsByContainer.keySet(), deletedContainers, insertedContainers, sameContainers); - - for (String sameContainer : sameContainers) { - int ix = sameContainer.indexOf("-"); - String containerName = sameContainer.substring(ix + 1); - // we have this container is source and target - ImmutableList sourceVertices = sourceFieldsByContainer.get(sameContainer); - ImmutableList targetVertices = targetFieldsByContainer.get(sameContainer); - - ArrayList deletedFields = new ArrayList<>(); - ArrayList insertedFields = new ArrayList<>(); - HashBiMap sameFields = HashBiMap.create(); - diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); - - if (deletedFields.size() > insertedFields.size()) { - Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + type + "-"); - for (Vertex deletedField : deletedFields) { - isolatedTargetVerticesForFields.put(containerName, deletedField.get("name"), newTargetVertices); - } - } else if (insertedFields.size() > deletedFields.size()) { - Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + type + "-"); - for (Vertex insertedField : insertedFields) { - isolatedSourceVerticesForFields.put(containerName, insertedField.get("name"), newSourceVertices); - } - } - } - - Set insertedFields = new LinkedHashSet<>(); - Set deletedFields = new LinkedHashSet<>(); - for (String insertedContainer : insertedContainers) { - insertedFields.addAll(targetFieldsByContainer.get(insertedContainer)); - } - for (String deletedContainer : deletedContainers) { - deletedFields.addAll(sourceFieldsByContainer.get(deletedContainer)); - } - if (deletedFields.size() > insertedFields.size()) { - isolatedTargetVertices.put(type, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-FIELD-")); - } else if (insertedFields.size() > deletedFields.size()) { - isolatedSourceVertices.put(type, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-FIELD-")); - } - + handleFields(sourceGraph, + targetGraph, + isolatedSourceVertices, + isolatedTargetVertices, + isolatedTargetVerticesForFields, + isolatedSourceVerticesForFields); + } else if (INPUT_FIELD.equals(type)) { + handleInputFields(sourceGraph, + targetGraph, + isolatedSourceVertices, + isolatedTargetVertices, + isolatedTargetVerticesForInputFields, + isolatedSourceVerticesForInputFields); } else { Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); -// System.out.println("type: " + type + " source size: " + sourceVertices.size()); if (sourceVertices.size() > targetVertices.size()) { isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); } else if (targetVertices.size() > sourceVertices.size()) { isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-" + type + "-")); } } -// if (isNamedType(type)) { -// ArrayList deleted = new ArrayList<>(); -// ArrayList inserted = new ArrayList<>(); -// HashBiMap same = HashBiMap.create(); -// diffNamedList(sourceVertices, targetVertices, deleted, inserted, same); -// System.out.println(" " + type + " deleted " + deleted.size() + " inserted" + inserted.size() + " same " + same.size()); -// } } for (Map.Entry> entry : isolatedSourceVertices.entrySet()) { @@ -237,6 +188,14 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t Map> row = isolatedTargetVerticesForFields.row(containerName); targetGraph.addVertices(row.values().iterator().next()); } + for (String inputObjectName : isolatedSourceVerticesForInputFields.rowKeySet()) { + Map> row = isolatedSourceVerticesForInputFields.row(inputObjectName); + sourceGraph.addVertices(row.values().iterator().next()); + } + for (String inputObjectName : isolatedTargetVerticesForInputFields.rowKeySet()) { + Map> row = isolatedTargetVerticesForInputFields.row(inputObjectName); + targetGraph.addVertices(row.values().iterator().next()); + } Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); @@ -247,7 +206,14 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-builtin-")); } assertTrue(sourceGraph.size() == targetGraph.size()); - IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, isolatedTargetVertices, isolatedBuiltInSourceVertices, isolatedBuiltInTargetVertices, isolatedSourceVerticesForFields, isolatedTargetVerticesForFields); + IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, + isolatedTargetVertices, + isolatedBuiltInSourceVertices, + isolatedBuiltInTargetVertices, + isolatedSourceVerticesForFields, + isolatedTargetVerticesForFields, + isolatedSourceVerticesForInputFields, + isolatedTargetVerticesForInputFields); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); @@ -315,6 +281,133 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t return bestEdit.get(); } + private void handleInputFields(SchemaGraph sourceGraph, + SchemaGraph targetGraph, + Map> isolatedSourceVertices, + Map> isolatedTargetVertices, + Table> isolatedTargetVerticesForInputFields, + Table> isolatedSourceVerticesForInputFields) { + Stream sourceVerticesStream = sourceGraph.getVerticesByType(INPUT_FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); + Stream targetVerticesStream = targetGraph.getVerticesByType(INPUT_FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); + + Map> sourceFieldsByInputObject = FpKit.groupingBy(sourceVerticesStream, v -> { + Vertex inputObject = getInputObjectForInputField(v, sourceGraph); + return inputObject.getType() + "-" + inputObject.get("name"); + }); + Map> targetFieldsByInputObject = FpKit.groupingBy(targetVerticesStream, v -> { + Vertex inputObject = getInputObjectForInputField(v, targetGraph); + return inputObject.getType() + "-" + inputObject.get("name"); + }); + + List deletedInputObjects = new ArrayList<>(); + List insertedInputObjects = new ArrayList<>(); + List sameInputObjects = new ArrayList<>(); + diffNamedList(sourceFieldsByInputObject.keySet(), targetFieldsByInputObject.keySet(), deletedInputObjects, insertedInputObjects, sameInputObjects); + + for (String sameInputObject : sameInputObjects) { + int ix = sameInputObject.indexOf("-"); + String inputObjectName = sameInputObject.substring(ix + 1); + // we have this container is source and target + ImmutableList sourceVertices = sourceFieldsByInputObject.get(sameInputObject); + ImmutableList targetVertices = targetFieldsByInputObject.get(sameInputObject); + + ArrayList deletedFields = new ArrayList<>(); + ArrayList insertedFields = new ArrayList<>(); + HashBiMap sameFields = HashBiMap.create(); + diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); + + if (deletedFields.size() > insertedFields.size()) { + Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + INPUT_FIELD + "-"); + for (Vertex deletedField : deletedFields) { + isolatedTargetVerticesForInputFields.put(inputObjectName, deletedField.get("name"), newTargetVertices); + } + } else if (insertedFields.size() > deletedFields.size()) { + Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + INPUT_FIELD + "-"); + for (Vertex insertedField : insertedFields) { + isolatedSourceVerticesForInputFields.put(inputObjectName, insertedField.get("name"), newSourceVertices); + } + } + } + + Set insertedFields = new LinkedHashSet<>(); + Set deletedFields = new LinkedHashSet<>(); + for (String insertedInputObject : insertedInputObjects) { + insertedFields.addAll(targetFieldsByInputObject.get(insertedInputObject)); + } + for (String deletedInputObject : deletedInputObjects) { + deletedFields.addAll(sourceFieldsByInputObject.get(deletedInputObject)); + } + if (deletedFields.size() > insertedFields.size()) { + isolatedTargetVertices.put(INPUT_FIELD, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + INPUT_FIELD + "-")); + } else if (insertedFields.size() > deletedFields.size()) { + isolatedSourceVertices.put(INPUT_FIELD, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + INPUT_FIELD + "-")); + } + + + } + + private void handleFields(SchemaGraph sourceGraph, + SchemaGraph targetGraph, + Map> isolatedSourceVertices, + Map> isolatedTargetVertices, + Table> isolatedTargetVerticesForFields, + Table> isolatedSourceVerticesForFields) { + Stream sourceVerticesStream = sourceGraph.getVerticesByType(FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); + Stream targetVerticesStream = targetGraph.getVerticesByType(FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); + Map> sourceFieldsByContainer = FpKit.groupingBy(sourceVerticesStream, v -> { + Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); + return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); + }); + Map> targetFieldsByContainer = FpKit.groupingBy(targetVerticesStream, v -> { + Vertex fieldsContainer = getFieldsContainerForField(v, targetGraph); + return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); + }); + + List deletedContainers = new ArrayList<>(); + List insertedContainers = new ArrayList<>(); + List sameContainers = new ArrayList<>(); + diffNamedList(sourceFieldsByContainer.keySet(), targetFieldsByContainer.keySet(), deletedContainers, insertedContainers, sameContainers); + + for (String sameContainer : sameContainers) { + int ix = sameContainer.indexOf("-"); + String containerName = sameContainer.substring(ix + 1); + // we have this container is source and target + ImmutableList sourceVertices = sourceFieldsByContainer.get(sameContainer); + ImmutableList targetVertices = targetFieldsByContainer.get(sameContainer); + + ArrayList deletedFields = new ArrayList<>(); + ArrayList insertedFields = new ArrayList<>(); + HashBiMap sameFields = HashBiMap.create(); + diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); + + if (deletedFields.size() > insertedFields.size()) { + Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + FIELD + "-"); + for (Vertex deletedField : deletedFields) { + isolatedTargetVerticesForFields.put(containerName, deletedField.get("name"), newTargetVertices); + } + } else if (insertedFields.size() > deletedFields.size()) { + Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + FIELD + "-"); + for (Vertex insertedField : insertedFields) { + isolatedSourceVerticesForFields.put(containerName, insertedField.get("name"), newSourceVertices); + } + } + } + + Set insertedFields = new LinkedHashSet<>(); + Set deletedFields = new LinkedHashSet<>(); + for (String insertedContainer : insertedContainers) { + insertedFields.addAll(targetFieldsByContainer.get(insertedContainer)); + } + for (String deletedContainer : deletedContainers) { + deletedFields.addAll(sourceFieldsByContainer.get(deletedContainer)); + } + if (deletedFields.size() > insertedFields.size()) { + isolatedTargetVertices.put(FIELD, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + FIELD + "-")); + } else if (insertedFields.size() > deletedFields.size()) { + isolatedSourceVertices.put(FIELD, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + FIELD + "-")); + } + } + private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { Map vertexWeights = new LinkedHashMap<>(); @@ -718,19 +811,28 @@ static class IsolatedInfo { Map> isolatedTargetVertices; Set isolatedBuiltInSourceVertices; Set isolatedBuiltInTargetVertices; + Table> isolatedSourceVerticesForFields; Table> isolatedTargetVerticesForFields; + Table> isolatedSourceVerticesForInputFields; + Table> isolatedTargetVerticesForInputFields; + public IsolatedInfo(Map> isolatedSourceVertices, Map> isolatedTargetVertices, Set isolatedBuiltInSourceVertices, Set isolatedBuiltInTargetVertices, Table> isolatedSourceVerticesForFields, - Table> isolatedTargetVerticesForFields) { + Table> isolatedTargetVerticesForFields, + Table> isolatedSourceVerticesForInputFields, + Table> isolatedTargetVerticesForInputFields + ) { this.isolatedSourceVertices = isolatedSourceVertices; this.isolatedTargetVertices = isolatedTargetVertices; this.isolatedBuiltInSourceVertices = isolatedBuiltInSourceVertices; this.isolatedBuiltInTargetVertices = isolatedBuiltInTargetVertices; this.isolatedSourceVerticesForFields = isolatedSourceVerticesForFields; this.isolatedTargetVerticesForFields = isolatedTargetVerticesForFields; + this.isolatedSourceVerticesForInputFields = isolatedSourceVerticesForInputFields; + this.isolatedTargetVerticesForInputFields = isolatedTargetVerticesForInputFields; } } @@ -755,6 +857,8 @@ private boolean isMappingPossible(Vertex v, Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; Table> isolatedSourceVerticesForFields = isolatedInfo.isolatedSourceVerticesForFields; Table> isolatedTargetVerticesForFields = isolatedInfo.isolatedTargetVerticesForFields; + Table> isolatedSourceVerticesForInputFields = isolatedInfo.isolatedSourceVerticesForInputFields; + Table> isolatedTargetVerticesForInputFields = isolatedInfo.isolatedTargetVerticesForInputFields; if (v.isArtificialNode()) { if (u.isBuiltInType()) { @@ -768,6 +872,14 @@ private boolean isMappingPossible(Vertex v, return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); } } + if (u.getType().equals(INPUT_FIELD)) { + Vertex inputObject = getInputObjectForInputField(u, targetGraph); + String inputObjectName = inputObject.get("name"); + String fieldName = u.get("name"); + if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { + return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); + } + } return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); } } @@ -778,11 +890,19 @@ private boolean isMappingPossible(Vertex v, if (v.getType().equals(FIELD)) { Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); String containerName = fieldsContainer.get("name"); - String fieldName = u.get("name"); + String fieldName = v.get("name"); if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); } } + if (v.getType().equals(INPUT_FIELD)) { + Vertex inputObject = getInputObjectForInputField(v, sourceGraph); + String inputObjectName = inputObject.get("name"); + String fieldName = v.get("name"); + if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { + return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); + } + } return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); } } @@ -1087,6 +1207,12 @@ private Vertex getFieldsContainerForField(Vertex field, SchemaGraph schemaGraph) return adjacentVertices.get(0); } + private Vertex getInputObjectForInputField(Vertex inputField, SchemaGraph schemaGraph) { + List adjacentVertices = schemaGraph.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); + assertTrue(adjacentVertices.size() == 1, () -> format("No input object found for %s", inputField)); + return adjacentVertices.get(0); + } + private Vertex getEnum(Vertex enumValue, SchemaGraph schemaGraph) { List adjacentVertices = schemaGraph.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for value %s", enumValue)); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 4339e359d0..8651f1a7fa 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -7,11 +7,9 @@ import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer import graphql.util.TraversalControl import graphql.util.TraverserContext -import spock.lang.Ignore import spock.lang.Specification import static graphql.TestUtil.schema -import static graphql.TestUtil.schemaFromResource class SchemaDiffingTest extends Specification { @@ -364,7 +362,6 @@ class SchemaDiffingTest extends Specification { } - @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) @@ -542,7 +539,7 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) - diff.each {println it} + diff.each { println it } then: diff.size() == 59 @@ -890,6 +887,48 @@ class SchemaDiffingTest extends Specification { operations.size() == 4 } + def "input fields"() { + given: + def schema1 = schema(""" + type Query { + foo(arg: I1, arg2: I2): String + } + input I1 { + f1: String + f2: String + } + input I2 { + g1: String + g2: String + } + """) + def schema2 = schema(""" + type Query { + foo(arg: I1,arg2: I2 ): String + } + input I1 { + f1: String + } + input I2 { + g2: String + g3: String + g4: String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + /** + * The test here is that f2 is deleted and one g is renamed and g3 is inserted. + * It would be less operations with f2 renamed to g3, but this would defy expectations. + * + */ + operations.size() == 7 + } + def "adding enum value"() { given: def schema1 = schema(""" From e0ae0c52a1ede2289ef47839bd65e679118bddd0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 9 Feb 2022 19:05:20 +1100 Subject: [PATCH 042/294] reworked isolated vertices handling. --- .../diffing/FillupIsolatedVertices.java | 374 ++++++++++++++ src/main/java/graphql/schema/diffing/Old.java | 120 ----- .../graphql/schema/diffing/SchemaDiffing.java | 484 ++++-------------- .../graphql/schema/diffing/SchemaGraph.java | 40 +- .../java/graphql/schema/diffing/Vertex.java | 20 +- src/main/java/graphql/util/FpKit.java | 8 + .../schema/diffing/SchemaDiffingTest.groovy | 34 ++ 7 files changed, 550 insertions(+), 530 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java delete mode 100644 src/main/java/graphql/schema/diffing/Old.java diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java new file mode 100644 index 0000000000..5b457bd346 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -0,0 +1,374 @@ +package graphql.schema.diffing; + +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import graphql.util.FpKit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static graphql.schema.diffing.SchemaDiffing.diffNamedList; +import static graphql.schema.diffing.SchemaDiffing.diffVertices; +import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.FIELD; +import static graphql.schema.diffing.SchemaGraphFactory.INPUT_FIELD; +import static graphql.util.FpKit.concat; + +public class FillupIsolatedVertices { + + SchemaGraph sourceGraph; + SchemaGraph targetGraph; + IsolatedVertices isolatedVertices; + + static Map> typeContexts = new LinkedHashMap<>(); + + static { + typeContexts.put(FIELD, fieldContext()); + typeContexts.put(INPUT_FIELD, inputFieldContexts()); + typeContexts.put(ARGUMENT, argumentsForFieldsContexts()); + } + + private static List fieldContext() { + IsolatedVertexContext field = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return FIELD.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + IsolatedVertexContext container = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex field, SchemaGraph schemaGraph) { + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(field); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(field, container); + return contexts; +// calc(contexts, FIELD); + } + + private static List argumentsForFieldsContexts() { + + IsolatedVertexContext argument = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return ARGUMENT.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + + IsolatedVertexContext field = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { + Vertex field = schemaGraph.getFieldOrDirectiveForArgument(argument); + return field.getName(); + } + + @Override + public boolean filter(Vertex argument, SchemaGraph schemaGraph) { + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); + return fieldOrDirective.getType().equals(FIELD); + } + }; + IsolatedVertexContext container = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { + Vertex field = schemaGraph.getFieldOrDirectiveForArgument(argument); + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(field); + // can be Interface or Object + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + } + + @Override + public boolean filter(Vertex argument, SchemaGraph schemaGraph) { + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); + return fieldOrDirective.getType().equals(FIELD); + } + }; + List contexts = Arrays.asList(argument, container, field); + return contexts; + } + + + public FillupIsolatedVertices(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + this.sourceGraph = sourceGraph; + this.targetGraph = targetGraph; + this.isolatedVertices = new IsolatedVertices(); + } + + public void ensureGraphAreSameSize() { + calcIsolatedVertices(typeContexts.get(FIELD), FIELD); + calcIsolatedVertices(typeContexts.get(ARGUMENT), ARGUMENT); + calcIsolatedVertices(typeContexts.get(INPUT_FIELD), INPUT_FIELD); + + sourceGraph.addVertices(isolatedVertices.allIsolatedSource); + sourceGraph.addVertices(isolatedVertices.allIsolatedTarget); + + if (sourceGraph.size() < targetGraph.size()) { + isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); + } else if (sourceGraph.size() > targetGraph.size()) { + isolatedVertices.isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-isolated-builtin-")); + } + + System.out.println(isolatedVertices); + } + + private static List inputFieldContexts() { + IsolatedVertexContext inputFieldContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return INPUT_FIELD.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + IsolatedVertexContext inputObjectContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + Vertex inputObject = schemaGraph.getInputObjectForInputField(vertex); + return inputObject.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(inputFieldContext, inputObjectContext); + return contexts; + } + + public abstract static class IsolatedVertexContext { +// // it is always by type first +// final String type; +// // then a list of names +// List subContextIds = new ArrayList<>(); +// +// public IsolatedVertexContext(String type) { +// this.type = type; +// } +// +// public static IsolatedVertexContext newContext(String type) { +// return new IsolatedVertexContext(type); +// } +// +// public static IsolatedVertexContext newContext(String type, String subContext1, String subContext2) { +// IsolatedVertexContext result = new IsolatedVertexContext(type); +// result.subContextIds.add(subContext1); +// result.subContextIds.add(subContext2); +// return result; +// } +// +// public static IsolatedVertexContext newContext(String type, String subContext1, String subContext2, String subContext3) { +// IsolatedVertexContext result = new IsolatedVertexContext(type); +// result.subContextIds.add(subContext1); +// result.subContextIds.add(subContext2); +// result.subContextIds.add(subContext3); +// return result; +// } +// +// public static IsolatedVertexContext newContext(String type, String subContext1) { +// IsolatedVertexContext result = new IsolatedVertexContext(type); +// result.subContextIds.add(subContext1); +// return result; +// } + + public abstract String idForVertex(Vertex vertex, SchemaGraph schemaGraph); + + public abstract boolean filter(Vertex vertex, SchemaGraph schemaGraph); + } + + /** + * This is all about which vertices are allowed to map to which isolated vertices. + * It maps a "context" to a list of isolated vertices. + * Contexts are "InputField", "foo.InputObject.InputField" + */ + public class IsolatedVertices { + + public Multimap contextToIsolatedSourceVertices = HashMultimap.create(); + public Multimap contextToIsolatedTargetVertices = HashMultimap.create(); + + public Set allIsolatedSource = new LinkedHashSet<>(); + public Set allIsolatedTarget = new LinkedHashSet<>(); + + public final Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); + public final Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); + + +// public void putSource(Object contextId, Vertex v) { +// contextToIsolatedSourceVertices.put(contextId, v); +// +// } + + public void putSource(Object contextId, Collection isolatedSourcedVertices) { + contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); + allIsolatedSource.addAll(isolatedSourcedVertices); + } + +// public void putTarget(Object contextId, Vertex v) { +// contextToIsolatedTargetVertices.put(contextId, v); +// } + + public void putTarget(Object contextId, Collection isolatedTargetVertices) { + contextToIsolatedTargetVertices.putAll(contextId, isolatedTargetVertices); + allIsolatedTarget.addAll(isolatedTargetVertices); + } + + public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Vertex targetVertex) { + List contexts = typeContexts.get(targetVertex.getType()); + if (contexts == null) { + return true; + } + List contextForVertex = new ArrayList<>(); + for (IsolatedVertexContext isolatedVertexContext : contexts) { + contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); + } + contextForVertex.add(targetVertex.getName()); + System.out.println(contextForVertex); + while (contextForVertex.size() > 0) { + if (isolatedVertices.contextToIsolatedSourceVertices.containsKey(contextForVertex)) { + return isolatedVertices.contextToIsolatedSourceVertices.get(contextForVertex).contains(isolatedSourceVertex); + } + contextForVertex.remove(contextForVertex.size() - 1); + } + return false; + } + + public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isolatedTargetVertex) { + List contexts = typeContexts.get(sourceVertex.getType()); + if (contexts == null) { + return true; + } + List contextForVertex = new ArrayList<>(); + for (IsolatedVertexContext isolatedVertexContext : contexts) { + contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); + } + contextForVertex.add(sourceVertex.getName()); + while (contextForVertex.size() > 0) { + if (isolatedVertices.contextToIsolatedTargetVertices.containsKey(contextForVertex)) { + return isolatedVertices.contextToIsolatedTargetVertices.get(contextForVertex).contains(isolatedTargetVertex); + } + contextForVertex.remove(contextForVertex.size() - 1); + } + return false; + + } + } + + + public void calcIsolatedVertices(List contexts, String typeNameForDebug) { + Collection currentSourceVertices = sourceGraph.getVertices(); + Collection currentTargetVertices = targetGraph.getVertices(); + calcImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); + } + + private void calcImpl( + Collection currentSourceVertices, + Collection currentTargetVertices, + List contextId, + int contextIx, + List contexts, + Set usedSourceVertices, + Set usedTargetVertices, + String typeNameForDebug) { + + IsolatedVertexContext finalCurrentContext = contexts.get(contextIx); + Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, + v -> finalCurrentContext.filter(v, sourceGraph), + v -> finalCurrentContext.idForVertex(v, sourceGraph)); + Map> targetGroups = FpKit.filterAndGroupingBy(currentTargetVertices, + v -> finalCurrentContext.filter(v, targetGraph), + v -> finalCurrentContext.idForVertex(v, targetGraph)); + + List deletedContexts = new ArrayList<>(); + List insertedContexts = new ArrayList<>(); + List sameContexts = new ArrayList<>(); + diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); + for (String sameContext : sameContexts) { + ImmutableList sourceVertices = sourceGroups.get(sameContext); + ImmutableList targetVertices = targetGroups.get(sameContext); + List currentContextId = concat(contextId, sameContext); + if (contexts.size() > contextIx + 1) { + calcImpl(sourceVertices, targetVertices, currentContextId, contextIx + 1, contexts, usedSourceVertices, usedTargetVertices, typeNameForDebug); + } + + Set notUsedSource = new LinkedHashSet<>(sourceVertices); + notUsedSource.removeAll(usedSourceVertices); + Set notUsedTarget = new LinkedHashSet<>(targetVertices); + notUsedTarget.removeAll(usedTargetVertices); + + /** + * We know that the first context is just by type and we have all the remaining vertices of the same + * type here. + */ + if (contextIx == 0) { + if (notUsedSource.size() > notUsedTarget.size()) { + Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); + // all deleted vertices can map to all new TargetVertices + for (Vertex deletedVertex : notUsedSource) { + isolatedVertices.putTarget(concat(contextId, deletedVertex.getName()), newTargetVertices); + } + } else if (notUsedTarget.size() > notUsedSource.size()) { + Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); + // all inserted fields can map to all new source vertices + for (Vertex insertedVertex : notUsedTarget) { + isolatedVertices.putSource(concat(contextId, insertedVertex.getName()), newSourceVertices); + } + } + } else { + + ArrayList deletedVertices = new ArrayList<>(); + ArrayList insertedVertices = new ArrayList<>(); + HashBiMap sameVertices = HashBiMap.create(); + diffVertices(notUsedSource, notUsedTarget, deletedVertices, insertedVertices, sameVertices, vertex -> { + return concat(currentContextId, vertex.getName()); + }); + usedSourceVertices.addAll(sourceGroups.get(sameContext)); + usedTargetVertices.addAll(targetGroups.get(sameContext)); + if (notUsedSource.size() > notUsedTarget.size()) { + Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); + // all deleted vertices can map to all new TargetVertices + for (Vertex deletedVertex : notUsedSource) { + isolatedVertices.putTarget(concat(currentContextId, deletedVertex.getName()), newTargetVertices); + } + } else if (notUsedTarget.size() > notUsedSource.size()) { + Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); + // all inserted fields can map to all new source vertices + for (Vertex insertedVertex : notUsedTarget) { + isolatedVertices.putSource(concat(currentContextId, insertedVertex.getName()), newSourceVertices); + } + } + } + + } + + } + + +} diff --git a/src/main/java/graphql/schema/diffing/Old.java b/src/main/java/graphql/schema/diffing/Old.java deleted file mode 100644 index 2647f19404..0000000000 --- a/src/main/java/graphql/schema/diffing/Old.java +++ /dev/null @@ -1,120 +0,0 @@ -//package graphql.schema.diffing; -// -//public class Old { -//} - -/* - if (oldVersion) { - - while (!queue.isEmpty()) { - MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { -// System.out.println("skipping!"); - continue; - } - // generate sibling - if (mappingEntry.level > 0 && mappingEntry.candidates.size() > 0) { - // we need to remove the last mapping - Mapping parentMapping = mappingEntry.partialMapping.removeLastElement(); - System.out.println("generate sibling"); - genNextMapping(parentMapping, mappingEntry.level, mappingEntry.candidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); - } - // generate children - if (mappingEntry.level < graphSize) { - // candidates are the vertices in target, of which are not used yet in partialMapping - Set childCandidates = new LinkedHashSet<>(targetGraph.getVertices()); - childCandidates.removeAll(mappingEntry.partialMapping.getTargets()); - System.out.println("generate child"); - genNextMapping(mappingEntry.partialMapping, mappingEntry.level + 1, childCandidates, queue, upperBoundCost, bestFullMapping, bestEdit, sourceGraph, targetGraph); - } - } - } else { - - - private void genNextMapping(Mapping partialMapping, - int level, - Set candidates, // changed in place on purpose - PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestMapping, - AtomicReference> bestEdit, - SchemaGraph sourceGraph, - SchemaGraph targetGraph) { - assertTrue(level - 1 == partialMapping.size()); - List sourceList = sourceGraph.getVertices(); - List targetList = targetGraph.getVertices(); - ArrayList availableTargetVertices = new ArrayList<>(targetList); - availableTargetVertices.removeAll(partialMapping.getTargets()); - assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); - // level starts at 1 ... therefore level - 1 is the current one we want to extend - Vertex v_i = sourceList.get(level - 1); - int costMatrixSize = sourceList.size() - level + 1; - double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - - - // we are skipping the first level -i indeces - int costCounter = 0; - int overallCount = (sourceList.size() - level) * availableTargetVertices.size(); - Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); - Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); - - for (int i = level - 1; i < sourceList.size(); i++) { - Vertex v = sourceList.get(i); - int j = 0; - for (Vertex u : availableTargetVertices) { - if (v == v_i && !candidates.contains(u)) { - costMatrix[i - level + 1][j] = Integer.MAX_VALUE; - } else { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); - costMatrix[i - level + 1][j] = cost; - } - j++; - } - } - // find out the best extension - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); - int[] assignments = hungarianAlgorithm.execute(); - - // calculating the lower bound costs for this extension: editorial cost for the partial mapping + value from the cost matrix for v_i - int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - double costMatrixSum = 0; - for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i][assignments[i]]; - } - double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; - - if (lowerBoundForPartialMapping < upperBound.doubleValue()) { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); - candidates.remove(bestExtensionTargetVertex); -// System.out.println("adding new entry " + getDebugMap(newMapping) + " at level " + level + " with candidates left: " + candidates.size() + " at lower bound: " + lowerBoundForPartialMapping); - queue.add(new MappingEntry(newMapping, level, lowerBoundForPartialMapping, candidates)); - - // we have a full mapping from the cost matrix - Mapping fullMapping = partialMapping.copy(); - for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); - } - assertTrue(fullMapping.size() == sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } else { -// System.out.println("to expensive cost for overall mapping " +); - } - } else { - int v_i_target_Index = assignments[0]; - Vertex bestExtensionTargetVertex = availableTargetVertices.get(v_i_target_Index); - Mapping newMapping = partialMapping.extendMapping(v_i, bestExtensionTargetVertex); -// System.out.println("not adding new entrie " + getDebugMap(newMapping) + " because " + lowerBoundForPartialMapping + " to high"); - } - } - - -*/ \ No newline at end of file diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1607c38f2c..65a56c7f3c 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,13 +1,9 @@ package graphql.schema.diffing; import com.google.common.collect.BiMap; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; -import com.google.common.collect.Table; import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.Assert; @@ -33,8 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.function.Function; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; @@ -52,7 +47,6 @@ import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; import static graphql.schema.diffing.SchemaGraphFactory.UNION; import static java.lang.String.format; -import static java.util.Collections.emptySet; import static java.util.Collections.synchronizedMap; public class SchemaDiffing { @@ -96,11 +90,11 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } - private void diffNamedList(Collection sourceVertices, - Collection targetVertices, - List deleted, // sourceVertices - List inserted, // targetVertices - BiMap same) { + public static void diffNamedVertices(Collection sourceVertices, + Collection targetVertices, + List deleted, // sourceVertices + List inserted, // targetVertices + BiMap same) { Map sourceByName = FpKit.groupingByUniqueKey(sourceVertices, vertex -> vertex.get("name")); Map targetByName = FpKit.groupingByUniqueKey(targetVertices, vertex -> vertex.get("name")); for (Vertex sourceVertex : sourceVertices) { @@ -119,11 +113,35 @@ private void diffNamedList(Collection sourceVertices, } } - private void diffNamedList(Set sourceNames, - Set targetNames, - List deleted, - List inserted, - List same) { + public static void diffVertices(Collection sourceVertices, + Collection targetVertices, + List deleted, // sourceVertices + List inserted, // targetVertices + BiMap same, + Function keyFn) { + Map sourceByKey = FpKit.groupingByUniqueKey(sourceVertices, keyFn); + Map targetByKey = FpKit.groupingByUniqueKey(targetVertices, keyFn); + for (Vertex sourceVertex : sourceVertices) { + Vertex targetVertex = targetByKey.get(keyFn.apply(sourceVertex)); + if (targetVertex == null) { + deleted.add(sourceVertex); + } else { + same.put(sourceVertex, targetVertex); + } + } + + for (Vertex targetVertex : targetVertices) { + if (sourceByKey.get(keyFn.apply(targetVertex)) == null) { + inserted.add(targetVertex); + } + } + } + + public static void diffNamedList(Set sourceNames, + Set targetNames, + List deleted, + List inserted, + List same) { for (String sourceName : sourceNames) { if (targetNames.contains(sourceName)) { same.add(sourceName); @@ -142,78 +160,11 @@ private void diffNamedList(Set sourceNames, List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); - Map> isolatedSourceVertices = new LinkedHashMap<>(); - Map> isolatedTargetVertices = new LinkedHashMap<>(); - Table> isolatedTargetVerticesForFields = HashBasedTable.create(); - Table> isolatedSourceVerticesForFields = HashBasedTable.create(); - Table> isolatedTargetVerticesForInputFields = HashBasedTable.create(); - Table> isolatedSourceVerticesForInputFields = HashBasedTable.create(); - for (String type : SchemaGraphFactory.ALL_TYPES) { - if (FIELD.equals(type)) { - handleFields(sourceGraph, - targetGraph, - isolatedSourceVertices, - isolatedTargetVertices, - isolatedTargetVerticesForFields, - isolatedSourceVerticesForFields); - } else if (INPUT_FIELD.equals(type)) { - handleInputFields(sourceGraph, - targetGraph, - isolatedSourceVertices, - isolatedTargetVertices, - isolatedTargetVerticesForInputFields, - isolatedSourceVerticesForInputFields); - } else { - Collection sourceVertices = sourceGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); - Collection targetVertices = targetGraph.getVerticesByType(type).stream().filter(vertex -> !vertex.isBuiltInType()).collect(Collectors.toList()); - if (sourceVertices.size() > targetVertices.size()) { - isolatedTargetVertices.put(type, Vertex.newArtificialNodes(sourceVertices.size() - targetVertices.size(), "target-artificial-" + type + "-")); - } else if (targetVertices.size() > sourceVertices.size()) { - isolatedSourceVertices.put(type, Vertex.newArtificialNodes(targetVertices.size() - sourceVertices.size(), "source-artificial-" + type + "-")); - } - } - } - - for (Map.Entry> entry : isolatedSourceVertices.entrySet()) { - sourceGraph.addVertices(entry.getValue()); - } - for (Map.Entry> entry : isolatedTargetVertices.entrySet()) { - targetGraph.addVertices(entry.getValue()); - } - for (String containerName : isolatedSourceVerticesForFields.rowKeySet()) { - Map> row = isolatedSourceVerticesForFields.row(containerName); - sourceGraph.addVertices(row.values().iterator().next()); - } - for (String containerName : isolatedTargetVerticesForFields.rowKeySet()) { - Map> row = isolatedTargetVerticesForFields.row(containerName); - targetGraph.addVertices(row.values().iterator().next()); - } - for (String inputObjectName : isolatedSourceVerticesForInputFields.rowKeySet()) { - Map> row = isolatedSourceVerticesForInputFields.row(inputObjectName); - sourceGraph.addVertices(row.values().iterator().next()); - } - for (String inputObjectName : isolatedTargetVerticesForInputFields.rowKeySet()) { - Map> row = isolatedTargetVerticesForInputFields.row(inputObjectName); - targetGraph.addVertices(row.values().iterator().next()); - } + FillupIsolatedVertices fillupIsolatedVertices = new FillupIsolatedVertices(sourceGraph, targetGraph); + fillupIsolatedVertices.ensureGraphAreSameSize(); + FillupIsolatedVertices.IsolatedVertices isolatedVertices = fillupIsolatedVertices.isolatedVertices; - Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); - Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); - // the only vertices left are built in types. - if (sourceGraph.size() < targetGraph.size()) { - isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-artificial-builtin-")); - } else if (sourceGraph.size() > targetGraph.size()) { - isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-artificial-builtin-")); - } assertTrue(sourceGraph.size() == targetGraph.size()); - IsolatedInfo isolatedInfo = new IsolatedInfo(isolatedSourceVertices, - isolatedTargetVertices, - isolatedBuiltInSourceVertices, - isolatedBuiltInTargetVertices, - isolatedSourceVerticesForFields, - isolatedTargetVerticesForFields, - isolatedSourceVerticesForInputFields, - isolatedTargetVerticesForInputFields); int graphSize = sourceGraph.size(); System.out.println("graph size: " + graphSize); @@ -265,7 +216,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t bestEdit, sourceGraph, targetGraph, - isolatedInfo + isolatedVertices ); } } @@ -281,133 +232,6 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t return bestEdit.get(); } - private void handleInputFields(SchemaGraph sourceGraph, - SchemaGraph targetGraph, - Map> isolatedSourceVertices, - Map> isolatedTargetVertices, - Table> isolatedTargetVerticesForInputFields, - Table> isolatedSourceVerticesForInputFields) { - Stream sourceVerticesStream = sourceGraph.getVerticesByType(INPUT_FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); - Stream targetVerticesStream = targetGraph.getVerticesByType(INPUT_FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); - - Map> sourceFieldsByInputObject = FpKit.groupingBy(sourceVerticesStream, v -> { - Vertex inputObject = getInputObjectForInputField(v, sourceGraph); - return inputObject.getType() + "-" + inputObject.get("name"); - }); - Map> targetFieldsByInputObject = FpKit.groupingBy(targetVerticesStream, v -> { - Vertex inputObject = getInputObjectForInputField(v, targetGraph); - return inputObject.getType() + "-" + inputObject.get("name"); - }); - - List deletedInputObjects = new ArrayList<>(); - List insertedInputObjects = new ArrayList<>(); - List sameInputObjects = new ArrayList<>(); - diffNamedList(sourceFieldsByInputObject.keySet(), targetFieldsByInputObject.keySet(), deletedInputObjects, insertedInputObjects, sameInputObjects); - - for (String sameInputObject : sameInputObjects) { - int ix = sameInputObject.indexOf("-"); - String inputObjectName = sameInputObject.substring(ix + 1); - // we have this container is source and target - ImmutableList sourceVertices = sourceFieldsByInputObject.get(sameInputObject); - ImmutableList targetVertices = targetFieldsByInputObject.get(sameInputObject); - - ArrayList deletedFields = new ArrayList<>(); - ArrayList insertedFields = new ArrayList<>(); - HashBiMap sameFields = HashBiMap.create(); - diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); - - if (deletedFields.size() > insertedFields.size()) { - Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + INPUT_FIELD + "-"); - for (Vertex deletedField : deletedFields) { - isolatedTargetVerticesForInputFields.put(inputObjectName, deletedField.get("name"), newTargetVertices); - } - } else if (insertedFields.size() > deletedFields.size()) { - Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + INPUT_FIELD + "-"); - for (Vertex insertedField : insertedFields) { - isolatedSourceVerticesForInputFields.put(inputObjectName, insertedField.get("name"), newSourceVertices); - } - } - } - - Set insertedFields = new LinkedHashSet<>(); - Set deletedFields = new LinkedHashSet<>(); - for (String insertedInputObject : insertedInputObjects) { - insertedFields.addAll(targetFieldsByInputObject.get(insertedInputObject)); - } - for (String deletedInputObject : deletedInputObjects) { - deletedFields.addAll(sourceFieldsByInputObject.get(deletedInputObject)); - } - if (deletedFields.size() > insertedFields.size()) { - isolatedTargetVertices.put(INPUT_FIELD, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + INPUT_FIELD + "-")); - } else if (insertedFields.size() > deletedFields.size()) { - isolatedSourceVertices.put(INPUT_FIELD, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + INPUT_FIELD + "-")); - } - - - } - - private void handleFields(SchemaGraph sourceGraph, - SchemaGraph targetGraph, - Map> isolatedSourceVertices, - Map> isolatedTargetVertices, - Table> isolatedTargetVerticesForFields, - Table> isolatedSourceVerticesForFields) { - Stream sourceVerticesStream = sourceGraph.getVerticesByType(FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); - Stream targetVerticesStream = targetGraph.getVerticesByType(FIELD).stream().filter(vertex -> !vertex.isBuiltInType()); - Map> sourceFieldsByContainer = FpKit.groupingBy(sourceVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); - return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); - }); - Map> targetFieldsByContainer = FpKit.groupingBy(targetVerticesStream, v -> { - Vertex fieldsContainer = getFieldsContainerForField(v, targetGraph); - return fieldsContainer.getType() + "-" + fieldsContainer.get("name"); - }); - - List deletedContainers = new ArrayList<>(); - List insertedContainers = new ArrayList<>(); - List sameContainers = new ArrayList<>(); - diffNamedList(sourceFieldsByContainer.keySet(), targetFieldsByContainer.keySet(), deletedContainers, insertedContainers, sameContainers); - - for (String sameContainer : sameContainers) { - int ix = sameContainer.indexOf("-"); - String containerName = sameContainer.substring(ix + 1); - // we have this container is source and target - ImmutableList sourceVertices = sourceFieldsByContainer.get(sameContainer); - ImmutableList targetVertices = targetFieldsByContainer.get(sameContainer); - - ArrayList deletedFields = new ArrayList<>(); - ArrayList insertedFields = new ArrayList<>(); - HashBiMap sameFields = HashBiMap.create(); - diffNamedList(sourceVertices, targetVertices, deletedFields, insertedFields, sameFields); - - if (deletedFields.size() > insertedFields.size()) { - Set newTargetVertices = Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + FIELD + "-"); - for (Vertex deletedField : deletedFields) { - isolatedTargetVerticesForFields.put(containerName, deletedField.get("name"), newTargetVertices); - } - } else if (insertedFields.size() > deletedFields.size()) { - Set newSourceVertices = Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + FIELD + "-"); - for (Vertex insertedField : insertedFields) { - isolatedSourceVerticesForFields.put(containerName, insertedField.get("name"), newSourceVertices); - } - } - } - - Set insertedFields = new LinkedHashSet<>(); - Set deletedFields = new LinkedHashSet<>(); - for (String insertedContainer : insertedContainers) { - insertedFields.addAll(targetFieldsByContainer.get(insertedContainer)); - } - for (String deletedContainer : deletedContainers) { - deletedFields.addAll(sourceFieldsByContainer.get(deletedContainer)); - } - if (deletedFields.size() > insertedFields.size()) { - isolatedTargetVertices.put(FIELD, Vertex.newArtificialNodes(deletedFields.size() - insertedFields.size(), "target-artificial-" + FIELD + "-")); - } else if (insertedFields.size() > deletedFields.size()) { - isolatedSourceVertices.put(FIELD, Vertex.newArtificialNodes(insertedFields.size() - deletedFields.size(), "source-artificial-" + FIELD + "-")); - } - } - private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { Map vertexWeights = new LinkedHashMap<>(); @@ -464,7 +288,7 @@ private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } - if (vertex.isArtificialNode()) { + if (vertex.isIsolated()) { return Integer.MIN_VALUE + 2; } return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); @@ -475,7 +299,7 @@ private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } - if (vertex.isArtificialNode()) { + if (vertex.isIsolated()) { return Integer.MIN_VALUE + 2; } List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); @@ -512,7 +336,7 @@ private void generateChildren(MappingEntry parentEntry, AtomicReference> bestEdit, SchemaGraph sourceGraph, SchemaGraph targetGraph, - IsolatedInfo isolatedInfo + FillupIsolatedVertices.IsolatedVertices isolatedInfo ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; @@ -715,25 +539,6 @@ private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignment return costMatrixSum; } -// private Vertex findMatchingVertex(Vertex v_i, List availableTargetVertices, SchemaGraph -// sourceGraph, SchemaGraph targetGraph) { -// String viType = v_i.getType(); -// HashMultiset viAdjacentEdges = HashMultiset.create(sourceGraph.getAdjacentEdges(v_i).stream().map(edge -> edge.getLabel()).collect(Collectors.toList())); -// if (viType.equals(SchemaGraphFactory.OBJECT) || viType.equals(INTERFACE) || viType.equals(UNION) || viType.equals(INPUT_OBJECT)) { -// for (Vertex targetVertex : availableTargetVertices) { -// if (v_i.isEqualTo(targetVertex)) { -// // check if edges are the same -// HashMultiset adjacentEdges = HashMultiset.create(targetGraph.getAdjacentEdges(targetVertex).stream().map(Edge::getLabel).collect(Collectors.toList())); -// if (viAdjacentEdges.equals(adjacentEdges)) { -// return targetVertex; -// } -// } -// -// } -// } -// return null; -// } - private List getDebugMap(Mapping mapping) { List result = new ArrayList<>(); // if (mapping.size() > 0) { @@ -758,9 +563,9 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so // Vertex changing (relabeling) boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); if (!equalNodes) { - if (sourceVertex.isArtificialNode()) { + if (sourceVertex.isIsolated()) { editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_VERTEX, "Insert" + targetVertex, targetVertex)); - } else if (targetVertex.isArtificialNode()) { + } else if (targetVertex.isIsolated()) { editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_VERTEX, "Delete " + sourceVertex, sourceVertex)); } else { editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex, Arrays.asList(sourceVertex, targetVertex))); @@ -806,104 +611,70 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph so private Map forcedMatchingCache = synchronizedMap(new LinkedHashMap<>()); - static class IsolatedInfo { - Map> isolatedSourceVertices; - Map> isolatedTargetVertices; - Set isolatedBuiltInSourceVertices; - Set isolatedBuiltInTargetVertices; - - Table> isolatedSourceVerticesForFields; - Table> isolatedTargetVerticesForFields; - - Table> isolatedSourceVerticesForInputFields; - Table> isolatedTargetVerticesForInputFields; - - public IsolatedInfo(Map> isolatedSourceVertices, Map> isolatedTargetVertices, Set isolatedBuiltInSourceVertices, - Set isolatedBuiltInTargetVertices, - Table> isolatedSourceVerticesForFields, - Table> isolatedTargetVerticesForFields, - Table> isolatedSourceVerticesForInputFields, - Table> isolatedTargetVerticesForInputFields - ) { - this.isolatedSourceVertices = isolatedSourceVertices; - this.isolatedTargetVertices = isolatedTargetVertices; - this.isolatedBuiltInSourceVertices = isolatedBuiltInSourceVertices; - this.isolatedBuiltInTargetVertices = isolatedBuiltInTargetVertices; - this.isolatedSourceVerticesForFields = isolatedSourceVerticesForFields; - this.isolatedTargetVerticesForFields = isolatedTargetVerticesForFields; - this.isolatedSourceVerticesForInputFields = isolatedSourceVerticesForInputFields; - this.isolatedTargetVerticesForInputFields = isolatedTargetVerticesForInputFields; - } - } - private boolean isMappingPossible(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph, Set partialMappingTargetSet, - IsolatedInfo isolatedInfo + FillupIsolatedVertices.IsolatedVertices isolatedInfo ) { Vertex forcedMatch = forcedMatchingCache.get(v); if (forcedMatch != null) { return forcedMatch == u; } - if (v.isArtificialNode() && u.isArtificialNode()) { + if (v.isIsolated() && u.isIsolated()) { return false; } - Map> isolatedSourceVertices = isolatedInfo.isolatedSourceVertices; - Map> isolatedTargetVertices = isolatedInfo.isolatedTargetVertices; Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; - Table> isolatedSourceVerticesForFields = isolatedInfo.isolatedSourceVerticesForFields; - Table> isolatedTargetVerticesForFields = isolatedInfo.isolatedTargetVerticesForFields; - Table> isolatedSourceVerticesForInputFields = isolatedInfo.isolatedSourceVerticesForInputFields; - Table> isolatedTargetVerticesForInputFields = isolatedInfo.isolatedTargetVerticesForInputFields; - if (v.isArtificialNode()) { + if (v.isIsolated()) { if (u.isBuiltInType()) { return isolatedBuiltInSourceVertices.contains(v); } else { - if (u.getType().equals(FIELD)) { - Vertex fieldsContainer = getFieldsContainerForField(u, targetGraph); - String containerName = fieldsContainer.get("name"); - String fieldName = u.get("name"); - if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { - return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); - } - } - if (u.getType().equals(INPUT_FIELD)) { - Vertex inputObject = getInputObjectForInputField(u, targetGraph); - String inputObjectName = inputObject.get("name"); - String fieldName = u.get("name"); - if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { - return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); - } - } - return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); + return isolatedInfo.mappingPossibleForIsolatedSource(v, u); +// if (u.getType().equals(FIELD)) { +// Vertex fieldsContainer = targetGraph.getFieldsContainerForField(u); +// String containerName = fieldsContainer.get("name"); +// String fieldName = u.get("name"); +// if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { +// return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); +// } +// } +// if (u.getType().equals(INPUT_FIELD)) { +// Vertex inputObject = targetGraph.getInputObjectForInputField(u); +// String inputObjectName = inputObject.get("name"); +// String fieldName = u.get("name"); +// if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { +// return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); +// } +// } +// return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); } } - if (u.isArtificialNode()) { + if (u.isIsolated()) { if (v.isBuiltInType()) { return isolatedBuiltInTargetVertices.contains(u); } else { - if (v.getType().equals(FIELD)) { - Vertex fieldsContainer = getFieldsContainerForField(v, sourceGraph); - String containerName = fieldsContainer.get("name"); - String fieldName = v.get("name"); - if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { - return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); - } - } - if (v.getType().equals(INPUT_FIELD)) { - Vertex inputObject = getInputObjectForInputField(v, sourceGraph); - String inputObjectName = inputObject.get("name"); - String fieldName = v.get("name"); - if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { - return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); - } - } - return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); + return isolatedInfo.mappingPossibleForIsolatedTarget(v, u); +// if (v.getType().equals(FIELD)) { +// Vertex fieldsContainer = sourceGraph.getFieldsContainerForField(v); +// String containerName = fieldsContainer.get("name"); +// String fieldName = v.get("name"); +// if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { +// return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); +// } +// } +// if (v.getType().equals(INPUT_FIELD)) { +// Vertex inputObject = sourceGraph.getInputObjectForInputField(v); +// String inputObjectName = inputObject.get("name"); +// String fieldName = v.get("name"); +// if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { +// return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); +// } +// } +// return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); } } // the types of the vertices need to match: we don't allow to change the type @@ -939,78 +710,6 @@ private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, } return null; -// if (DIRECTIVE.equals(v.getType())) { -// Vertex targetVertex = targetGraph.getDirective(v.get("name")); -// if (targetVertex != null) { -// forcedMatchingCache.put(v, targetVertex); -// forcedMatchingCache.put(targetVertex, v); -// return u == targetVertex; -// } -// } -// -// if (DUMMY_TYPE_VERTEX.equals(v.getType())) { -// List adjacentVertices = sourceGraph.getAdjacentVertices(v); -// for (Vertex vertex : adjacentVertices) { -// if (vertex.getType().equals(FIELD)) { -// Vertex matchingTargetField = findMatchingTargetField(vertex, sourceGraph, targetGraph); -// if (matchingTargetField != null) { -// Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetField, targetGraph); -// forcedMatchingCache.put(v, dummyTypeVertex); -// forcedMatchingCache.put(dummyTypeVertex, v); -// return u == dummyTypeVertex; -// } -// } else if (vertex.getType().equals(INPUT_FIELD)) { -// Vertex matchingTargetInputField = findMatchingTargetInputField(vertex, sourceGraph, targetGraph); -// if (matchingTargetInputField != null) { -// Vertex dummyTypeVertex = getDummyTypeVertex(matchingTargetInputField, targetGraph); -// forcedMatchingCache.put(v, dummyTypeVertex); -// forcedMatchingCache.put(dummyTypeVertex, v); -// return u == dummyTypeVertex; -// } -// } -// } -// } -// if (INPUT_FIELD.equals(v.getType())) { -// Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); -// if (matchingTargetInputField != null) { -// forcedMatchingCache.put(v, matchingTargetInputField); -// forcedMatchingCache.put(matchingTargetInputField, v); -// return u == matchingTargetInputField; -// } -// } -// if (FIELD.equals(v.getType())) { -// Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); -// if (matchingTargetField != null) { -// forcedMatchingCache.put(v, matchingTargetField); -// forcedMatchingCache.put(matchingTargetField, v); -// return u == matchingTargetField; -// } -// } -// if (ENUM_VALUE.equals(v.getType())) { -// Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); -// if (matchingTargetEnumValue != null) { -// forcedMatchingCache.put(v, matchingTargetEnumValue); -// forcedMatchingCache.put(matchingTargetEnumValue, v); -// return u == matchingTargetEnumValue; -// } -// } -// if (ARGUMENT.equals(v.getType())) { -// Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); -// if (matchingTargetArgument != null) { -// forcedMatchingCache.put(v, matchingTargetArgument); -// forcedMatchingCache.put(matchingTargetArgument, v); -// return u == matchingTargetArgument; -// } -// } -// if (APPLIED_ARGUMENT.equals(v.getType())) { -// Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); -// if (matchingTargetArgument != null) { -// forcedMatchingCache.put(v, matchingTargetArgument); -// forcedMatchingCache.put(matchingTargetArgument, v); -// return u == matchingTargetArgument; -// } -// } -// return null; } private Vertex findMatchingTargetVertex(Vertex v, SchemaGraph sourceGraph, SchemaGraph targetGraph) { @@ -1155,7 +854,7 @@ private Vertex findMatchingAppliedArgument(Vertex appliedArgument, SchemaGraph s } private Vertex findMatchingTargetField(Vertex field, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex sourceFieldsContainer = getFieldsContainerForField(field, sourceGraph); + Vertex sourceFieldsContainer = sourceGraph.getFieldsContainerForField(field); Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { Vertex matchingField = getFieldForContainer(targetFieldsContainerWithSameName, field.get("name"), targetGraph); @@ -1201,17 +900,6 @@ private Vertex getArgumentForFieldOrDirective(Vertex fieldOrDirective, String ar return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); } - private Vertex getFieldsContainerForField(Vertex field, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(field, vertex -> vertex.getType().equals(OBJECT) || vertex.getType().equals(INTERFACE)); - assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", field)); - return adjacentVertices.get(0); - } - - private Vertex getInputObjectForInputField(Vertex inputField, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); - assertTrue(adjacentVertices.size() == 1, () -> format("No input object found for %s", inputField)); - return adjacentVertices.get(0); - } private Vertex getEnum(Vertex enumValue, SchemaGraph schemaGraph) { List adjacentVertices = schemaGraph.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); @@ -1232,7 +920,7 @@ private double calcLowerBoundMappingCost(Vertex v, Set partialMappingSourceSet, List partialMappingTargetList, Set partialMappingTargetSet, - IsolatedInfo isolatedInfo + FillupIsolatedVertices.IsolatedVertices isolatedInfo ) { if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedInfo)) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index e0dddc36df..dc5bfbf367 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -7,9 +7,22 @@ import com.google.common.collect.Table; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Predicate; +import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; +import static graphql.schema.diffing.SchemaGraphFactory.FIELD; +import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; +import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; +import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; +import static java.lang.String.format; + public class SchemaGraph { private List vertices = new ArrayList<>(); @@ -34,8 +47,9 @@ public void addVertex(Vertex vertex) { vertices.add(vertex); typeToVertices.put(vertex.getType(), vertex); } + public void addVertices(Collection vertices) { - for(Vertex vertex: vertices) { + for (Vertex vertex : vertices) { this.vertices.add(vertex); typeToVertices.put(vertex.getType(), vertex); } @@ -129,10 +143,30 @@ public int size() { public List addIsolatedVertices(int count, String debugPrefix) { List result = new ArrayList<>(); for (int i = 0; i < count; i++) { - Vertex isolatedVertex = Vertex.newArtificialNode(debugPrefix + i); + Vertex isolatedVertex = Vertex.newIsolatedNode(debugPrefix + i); vertices.add(isolatedVertex); result.add(isolatedVertex); } return result; } + + public Vertex getFieldOrDirectiveForArgument(Vertex argument) { + List adjacentVertices = getAdjacentVertices(argument, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(DIRECTIVE)); + assertTrue(adjacentVertices.size() == 1, () -> format("No field or directive found for %s", argument)); + return adjacentVertices.get(0); + } + + public Vertex getFieldsContainerForField(Vertex field) { + List adjacentVertices = getAdjacentVertices(field, vertex -> vertex.getType().equals(OBJECT) || vertex.getType().equals(INTERFACE)); + assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", field)); + return adjacentVertices.get(0); + } + + public Vertex getInputObjectForInputField(Vertex inputField) { + List adjacentVertices = this.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); + assertTrue(adjacentVertices.size() == 1, () -> format("No input object found for %s", inputField)); + return adjacentVertices.get(0); + } + + } diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index af5e0c2513..bc042ec326 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -1,9 +1,7 @@ package graphql.schema.diffing; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -13,22 +11,22 @@ public class Vertex { private String type; private Map properties = new LinkedHashMap<>(); private String debugName; - private boolean artificialNode; + private boolean isolated; private boolean builtInType; - public static Vertex newArtificialNode(String debugName) { + public static Vertex newIsolatedNode(String debugName) { Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName); - vertex.artificialNode = true; + vertex.isolated = true; return vertex; } - public static Set newArtificialNodes(int count, String debugName) { + public static Set newIsolatedNodes(int count, String debugName) { Set result = new LinkedHashSet<>(); for (int i = 1; i <= count; i++) { Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName + i); - vertex.artificialNode = true; + vertex.isolated = true; result.add(vertex); } return result; @@ -39,8 +37,8 @@ public Vertex(String type, String debugName) { this.debugName = debugName; } - public boolean isArtificialNode() { - return artificialNode; + public boolean isIsolated() { + return isolated; } public void add(String propName, Object propValue) { @@ -59,6 +57,10 @@ public T getProperty(String name) { return (T) properties.get(name); } + public String getName() { + return (String) properties.get("name"); + } + public Map getProperties() { return properties; } diff --git a/src/main/java/graphql/util/FpKit.java b/src/main/java/graphql/util/FpKit.java index b84831617c..a33ebae513 100644 --- a/src/main/java/graphql/util/FpKit.java +++ b/src/main/java/graphql/util/FpKit.java @@ -48,9 +48,16 @@ public static Map> groupingBy(Collection return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); } + public static Map> filterAndGroupingBy(Collection list, + Predicate predicate, + Function function) { + return list.stream().filter(predicate).collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); + } + public static Map> groupingBy(Stream stream, Function function) { return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); } + public static Map groupingByUniqueKey(Collection list, Function keyFunction) { return list.stream().collect(Collectors.toMap( keyFunction, @@ -59,6 +66,7 @@ public static Map groupingByUniqueKey(Collection list, LinkedHashMap::new) ); } + public static Map groupingByUniqueKey(Stream stream, Function keyFunction) { return stream.collect(Collectors.toMap( keyFunction, diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 8651f1a7fa..c879803319 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -929,6 +929,40 @@ class SchemaDiffingTest extends Specification { operations.size() == 7 } + def "arguments"() { + given: + def schema1 = schema(""" + type Query { + a(f1: String, f2:String): String + b(g1: String, g2:String): O1 + } + type O1 { + c(h1: String, h2:String): String + d(i1: String, i2:String): O1 + } + """) + def schema2 = schema(""" + type Query { + a(f1: String): String + b(g2: String, g3:String, g4: String): String + } + type O1 { + c(h1: String, h2:String): String + renamed(i2: String, i3:String): O1 + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + /** + * + */ + operations.size() == 7 + } + def "adding enum value"() { given: def schema1 = schema(""" From bca1f400db4c44d6b7c1c7a5fcf9d318cd2751a2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 9 Feb 2022 19:21:47 +1100 Subject: [PATCH 043/294] wip --- .../diffing/FillupIsolatedVertices.java | 222 +++++++++++++++++- 1 file changed, 214 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 5b457bd346..24403adbf0 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -4,6 +4,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; +import graphql.Assert; import graphql.util.FpKit; import java.util.ArrayList; @@ -18,9 +19,20 @@ import static graphql.schema.diffing.SchemaDiffing.diffNamedList; import static graphql.schema.diffing.SchemaDiffing.diffVertices; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; +import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; +import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; +import static graphql.schema.diffing.SchemaGraphFactory.ENUM; +import static graphql.schema.diffing.SchemaGraphFactory.ENUM_VALUE; import static graphql.schema.diffing.SchemaGraphFactory.FIELD; import static graphql.schema.diffing.SchemaGraphFactory.INPUT_FIELD; +import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; +import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; +import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; +import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; +import static graphql.schema.diffing.SchemaGraphFactory.UNION; import static graphql.util.FpKit.concat; public class FillupIsolatedVertices { @@ -33,8 +45,195 @@ public class FillupIsolatedVertices { static { typeContexts.put(FIELD, fieldContext()); - typeContexts.put(INPUT_FIELD, inputFieldContexts()); typeContexts.put(ARGUMENT, argumentsForFieldsContexts()); + typeContexts.put(INPUT_FIELD, inputFieldContexts()); + typeContexts.put(DUMMY_TYPE_VERTEX, dummyTypeContext()); + typeContexts.put(OBJECT, objectContext()); + typeContexts.put(INTERFACE, interfaceContext()); + typeContexts.put(UNION, unionContext()); + typeContexts.put(INPUT_OBJECT, inputObjectContext()); + typeContexts.put(SCALAR, scalarContext()); + typeContexts.put(ENUM, enumContext()); + typeContexts.put(ENUM_VALUE, enumValueContext()); + typeContexts.put(APPLIED_DIRECTIVE, appliedDirectiveContext()); + typeContexts.put(APPLIED_ARGUMENT, appliedArgumentContext()); + typeContexts.put(DIRECTIVE, directiveContext()); + } + + private static List dummyTypeContext() { + IsolatedVertexContext dummyType = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return DUMMY_TYPE_VERTEX.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(dummyType); + return contexts; + } + + private static List scalarContext() { + IsolatedVertexContext scalar = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return SCALAR.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(scalar); + return contexts; + } + + private static List inputObjectContext() { + IsolatedVertexContext inputObject = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return INPUT_OBJECT.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(inputObject); + return contexts; + } + + private static List objectContext() { + IsolatedVertexContext object = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return OBJECT.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(object); + return contexts; + } + + private static List enumContext() { + IsolatedVertexContext enumCtx = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return ENUM.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(enumCtx); + return contexts; + } + + private static List enumValueContext() { + IsolatedVertexContext enumValue = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return ENUM_VALUE.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(enumValue); + return contexts; + } + + private static List interfaceContext() { + IsolatedVertexContext interfaceContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return INTERFACE.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(interfaceContext); + return contexts; + } + + private static List unionContext() { + IsolatedVertexContext unionContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return UNION.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(unionContext); + return contexts; + } + + private static List directiveContext() { + IsolatedVertexContext directiveContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return DIRECTIVE.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(directiveContext); + return contexts; + } + + private static List appliedDirectiveContext() { + IsolatedVertexContext appliedDirective = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return APPLIED_DIRECTIVE.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(appliedDirective); + return contexts; + } + + private static List appliedArgumentContext() { + IsolatedVertexContext appliedArgument = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return APPLIED_ARGUMENT.equals(vertex.getType()) && !vertex.isBuiltInType(); + } + }; + List contexts = Arrays.asList(appliedArgument); + return contexts; } private static List fieldContext() { @@ -63,7 +262,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { }; List contexts = Arrays.asList(field, container); return contexts; -// calc(contexts, FIELD); } private static List argumentsForFieldsContexts() { @@ -123,6 +321,18 @@ public void ensureGraphAreSameSize() { calcIsolatedVertices(typeContexts.get(FIELD), FIELD); calcIsolatedVertices(typeContexts.get(ARGUMENT), ARGUMENT); calcIsolatedVertices(typeContexts.get(INPUT_FIELD), INPUT_FIELD); + calcIsolatedVertices(typeContexts.get(DUMMY_TYPE_VERTEX), DUMMY_TYPE_VERTEX); + calcIsolatedVertices(typeContexts.get(OBJECT), OBJECT); + calcIsolatedVertices(typeContexts.get(INTERFACE), INTERFACE); + calcIsolatedVertices(typeContexts.get(UNION), UNION); + calcIsolatedVertices(typeContexts.get(INPUT_OBJECT), INPUT_OBJECT); + calcIsolatedVertices(typeContexts.get(SCALAR), SCALAR); + calcIsolatedVertices(typeContexts.get(ENUM), ENUM); + calcIsolatedVertices(typeContexts.get(ENUM_VALUE), ENUM_VALUE); + calcIsolatedVertices(typeContexts.get(APPLIED_DIRECTIVE), APPLIED_DIRECTIVE); + calcIsolatedVertices(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT); + calcIsolatedVertices(typeContexts.get(DIRECTIVE), DIRECTIVE); + sourceGraph.addVertices(isolatedVertices.allIsolatedSource); sourceGraph.addVertices(isolatedVertices.allIsolatedTarget); @@ -242,9 +452,7 @@ public void putTarget(Object contextId, Collection isolatedTargetVertice public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Vertex targetVertex) { List contexts = typeContexts.get(targetVertex.getType()); - if (contexts == null) { - return true; - } + Assert.assertNotNull(contexts); List contextForVertex = new ArrayList<>(); for (IsolatedVertexContext isolatedVertexContext : contexts) { contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); @@ -262,9 +470,7 @@ public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Ver public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isolatedTargetVertex) { List contexts = typeContexts.get(sourceVertex.getType()); - if (contexts == null) { - return true; - } + Assert.assertNotNull(contexts); List contextForVertex = new ArrayList<>(); for (IsolatedVertexContext isolatedVertexContext : contexts) { contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); From 8b55bf10ba12030692f4356af47a7b8afb81a177 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 04:50:55 +1100 Subject: [PATCH 044/294] wip --- .../schema/diffing/FillupIsolatedVertices.java | 13 ++++++++----- .../java/graphql/schema/diffing/SchemaDiffing.java | 6 ++++-- src/main/java/graphql/schema/diffing/Vertex.java | 4 +++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 24403adbf0..de3329ad16 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -457,8 +457,9 @@ public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Ver for (IsolatedVertexContext isolatedVertexContext : contexts) { contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); } - contextForVertex.add(targetVertex.getName()); - System.out.println(contextForVertex); + if (!targetVertex.getType().equals(DUMMY_TYPE_VERTEX)) { + contextForVertex.add(targetVertex.getName()); + } while (contextForVertex.size() > 0) { if (isolatedVertices.contextToIsolatedSourceVertices.containsKey(contextForVertex)) { return isolatedVertices.contextToIsolatedSourceVertices.get(contextForVertex).contains(isolatedSourceVertex); @@ -475,7 +476,9 @@ public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isol for (IsolatedVertexContext isolatedVertexContext : contexts) { contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); } - contextForVertex.add(sourceVertex.getName()); + if (!sourceVertex.getType().equals(DUMMY_TYPE_VERTEX)) { + contextForVertex.add(sourceVertex.getName()); + } while (contextForVertex.size() > 0) { if (isolatedVertices.contextToIsolatedTargetVertices.containsKey(contextForVertex)) { return isolatedVertices.contextToIsolatedTargetVertices.get(contextForVertex).contains(isolatedTargetVertex); @@ -538,13 +541,13 @@ private void calcImpl( Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); // all deleted vertices can map to all new TargetVertices for (Vertex deletedVertex : notUsedSource) { - isolatedVertices.putTarget(concat(contextId, deletedVertex.getName()), newTargetVertices); + isolatedVertices.putTarget(Arrays.asList(sameContext), newTargetVertices); } } else if (notUsedTarget.size() > notUsedSource.size()) { Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); // all inserted fields can map to all new source vertices for (Vertex insertedVertex : notUsedTarget) { - isolatedVertices.putSource(concat(contextId, insertedVertex.getName()), newSourceVertices); + isolatedVertices.putSource(Arrays.asList(sameContext), newSourceVertices); } } } else { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 65a56c7f3c..31522441d4 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -554,8 +554,10 @@ private List getDebugMap(Mapping mapping) { } // minimum number of edit operations for a full mapping - private int editorialCostForMapping(Mapping partialOrFullMapping, SchemaGraph sourceGraph, SchemaGraph - targetGraph, List editOperationsResult) { + private int editorialCostForMapping(Mapping partialOrFullMapping, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + List editOperationsResult) { int cost = 0; for (int i = 0; i < partialOrFullMapping.size(); i++) { Vertex sourceVertex = partialOrFullMapping.getSource(i); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index bc042ec326..386f31a5fe 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import graphql.Assert; + import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -58,7 +60,7 @@ public T getProperty(String name) { } public String getName() { - return (String) properties.get("name"); + return (String) Assert.assertNotNull(properties.get("name"), () -> String.format("should not call getName on %s", this)); } public Map getProperties() { From a1a45c534daa023bf4a846335684a11c946a91ca Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 07:43:13 +1100 Subject: [PATCH 045/294] fixing isolated vertices handling --- .../diffing/FillupIsolatedVertices.java | 18 +++++++++++++- .../graphql/schema/diffing/SchemaDiffing.java | 24 ++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index de3329ad16..928cf816ad 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -335,7 +335,7 @@ public void ensureGraphAreSameSize() { sourceGraph.addVertices(isolatedVertices.allIsolatedSource); - sourceGraph.addVertices(isolatedVertices.allIsolatedTarget); + targetGraph.addVertices(isolatedVertices.allIsolatedTarget); if (sourceGraph.size() < targetGraph.size()) { isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); @@ -518,6 +518,22 @@ private void calcImpl( List deletedContexts = new ArrayList<>(); List insertedContexts = new ArrayList<>(); List sameContexts = new ArrayList<>(); + + if (contextIx == 0) { + if (sourceGroups.size() == 0 && targetGroups.size() == 1) { + // we only have inserted elements + String context = targetGroups.keySet().iterator().next(); + int count = targetGroups.get(context).size(); + Set newSourceVertices = Vertex.newIsolatedNodes(count, "source-isolated-" + typeNameForDebug + "-"); + isolatedVertices.putSource(Arrays.asList(context), newSourceVertices); + } else if (sourceGroups.size() == 1 && targetGroups.size() == 0) { + // we only have deleted elements + String context = sourceGroups.keySet().iterator().next(); + int count = sourceGroups.get(context).size(); + Set newTargetVertices = Vertex.newIsolatedNodes(count, "target-isolated-" + typeNameForDebug + "-"); + isolatedVertices.putTarget(Arrays.asList(context), newTargetVertices); + } + } diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); for (String sameContext : sameContexts) { ImmutableList sourceVertices = sourceGroups.get(sameContext); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 31522441d4..088e7e2c78 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -400,18 +400,22 @@ private void generateChildren(MappingEntry parentEntry, int[] assignments = hungarianAlgorithm.execute(); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); +// if (costMatrixSum >= Integer.MAX_VALUE) { +// logUnmappable(costMatrixCopy, assignments, sourceList, availableTargetVertices, level); +// throw new RuntimeException("should not happen"); +// } - double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSum; + double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; int v_i_target_IndexSibling = assignments[0]; Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMappingSibling >= upperBound.doubleValue()) { + if (lowerBoundForPartialMapping >= upperBound.doubleValue()) { return; } - MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); + MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMapping); LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); // newMappingEntry.siblingsReady = new AtomicBoolean(); newMappingEntry.mappingEntriesSiblings = siblings; @@ -527,6 +531,8 @@ private void getSibling( System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } + } else { +// System.out.println("sibling not good enough"); } } @@ -539,6 +545,18 @@ private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignment return costMatrixSum; } + private void logUnmappable(AtomicDoubleArray[] costMatrix, int[] assignments, List sourceList, ArrayList availableTargetVertices, int level) { + for (int i = 0; i < assignments.length; i++) { + double value = costMatrix[i].get(assignments[i]); + if (value >= Integer.MAX_VALUE) { + System.out.println("i " + i + " can't mapped"); + Vertex v = sourceList.get(i + level - 1); + Vertex u = availableTargetVertices.get(assignments[i]); + System.out.println("from " + v + " to " + u); + } + } + } + private List getDebugMap(Mapping mapping) { List result = new ArrayList<>(); // if (mapping.size() > 0) { From e1dbb90bf9d87d606d5d0ea8b8ffedc60963672b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 12:16:22 +1100 Subject: [PATCH 046/294] tests --- .../graphql/schema/diffing/SchemaDiffingTest.groovy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index c879803319..3a1873cf60 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -958,9 +958,13 @@ class SchemaDiffingTest extends Specification { then: /** - * + * Query.f2 deleted + * O1.b.g1 => O1.b.g4 + * O1.d.i1 -> O.renamed.i3 + * O1.d => O1.renamed + * Inserted O1.b.g3 */ - operations.size() == 7 + operations.size() == 11 } def "adding enum value"() { From e00980fefbada7a072160c55c89cad72c983505b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 13:33:16 +1100 Subject: [PATCH 047/294] more test, directives handling --- .../diffing/FillupIsolatedVertices.java | 29 ++++++----- .../graphql/schema/diffing/SchemaGraph.java | 1 + .../schema/diffing/SchemaGraphFactory.java | 8 +-- .../schema/diffing/SchemaDiffingTest.groovy | 52 ++++++++++++++++++- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 928cf816ad..0c6f1b5921 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -278,35 +278,38 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - IsolatedVertexContext field = new IsolatedVertexContext() { + IsolatedVertexContext fieldOrDirective = new IsolatedVertexContext() { @Override public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { - Vertex field = schemaGraph.getFieldOrDirectiveForArgument(argument); - return field.getName(); + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); + return fieldOrDirective.getType() + "." + fieldOrDirective.getName(); } @Override public boolean filter(Vertex argument, SchemaGraph schemaGraph) { - Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); - return fieldOrDirective.getType().equals(FIELD); + return true; } }; - IsolatedVertexContext container = new IsolatedVertexContext() { + IsolatedVertexContext containerOrDirectiveHolder = new IsolatedVertexContext() { @Override public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { - Vertex field = schemaGraph.getFieldOrDirectiveForArgument(argument); - Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(field); - // can be Interface or Object - return fieldsContainer.getType() + "." + fieldsContainer.getName(); + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.getType().equals(FIELD)) { + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective); + // can be Interface or Object + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + } else { + // a directive doesn't have further context + return ""; + } } @Override public boolean filter(Vertex argument, SchemaGraph schemaGraph) { - Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); - return fieldOrDirective.getType().equals(FIELD); + return true; } }; - List contexts = Arrays.asList(argument, container, field); + List contexts = Arrays.asList(argument, containerOrDirectiveHolder, fieldOrDirective); return contexts; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index dc5bfbf367..905d78500c 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -16,6 +16,7 @@ import java.util.function.Predicate; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; import static graphql.schema.diffing.SchemaGraphFactory.FIELD; import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 6efe73e796..b5a78e53a8 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -355,13 +355,13 @@ private void cratedAppliedDirectives(Vertex from, List applied Vertex appliedDirectiveVertex = new Vertex(APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { - Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); - appliedArgumentVertex.add("name", appliedArgument.getName()); if (appliedArgument.hasSetValue()) { + Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); + appliedArgumentVertex.add("name", appliedArgument.getName()); appliedArgumentVertex.add("value", AstPrinter.printAst(ValuesResolver.valueToLiteral(appliedArgument.getArgumentValue(), appliedArgument.getType()))); + schemaGraph.addVertex(appliedArgumentVertex); + schemaGraph.addEdge(new Edge(appliedDirectiveVertex, appliedArgumentVertex)); } - schemaGraph.addVertex(appliedArgumentVertex); - schemaGraph.addEdge(new Edge(appliedDirectiveVertex, appliedArgumentVertex)); } schemaGraph.addVertex(appliedDirectiveVertex); schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 3a1873cf60..e087273d26 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -7,6 +7,7 @@ import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer import graphql.util.TraversalControl import graphql.util.TraverserContext +import spock.lang.Ignore import spock.lang.Specification import static graphql.TestUtil.schema @@ -929,7 +930,7 @@ class SchemaDiffingTest extends Specification { operations.size() == 7 } - def "arguments"() { + def "arguments in fields"() { given: def schema1 = schema(""" type Query { @@ -996,6 +997,55 @@ class SchemaDiffingTest extends Specification { operations.size() == 2 } + def "arguments in directives changed"() { + given: + def schema1 = schema(''' + directive @d(a1: String, a2: String) on FIELD_DEFINITION + type Query { + foo: String @d + } + ''') + def schema2 = schema(""" + directive @d(a1: String, a3: String, a4: String) on FIELD_DEFINITION + type Query { + foo: String @d + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + /** + * change argument, insert argument, new edge from Directive to new Argument + */ + operations.size() == 3 + } + + def "change applied argument"() { + given: + def schema1 = schema(''' + directive @d(a1: String, a2: String) on FIELD_DEFINITION + type Query { + foo: String @d(a1: "S1", a2: "S2") + } + ''') + def schema2 = schema(""" + directive @d(a1: String, a2: String) on FIELD_DEFINITION + type Query { + foo: String @d(a2: "S2Changed", a1: "S1Changed") + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 2 + } + def "with directives"() { given: def schema1 = schema(''' From 57cb92f81c60bc225413aa4d53b99f44898c0b6e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 14:31:14 +1100 Subject: [PATCH 048/294] applied arguments handling --- .../DefaultGraphEditOperationAnalyzer.java | 2 +- .../diffing/FillupIsolatedVertices.java | 116 ++++++++---------- .../graphql/schema/diffing/SchemaDiffing.java | 28 ++--- .../graphql/schema/diffing/SchemaGraph.java | 91 +++++++++++++- .../schema/diffing/SchemaGraphFactory.java | 61 +++------ .../java/graphql/schema/diffing/Vertex.java | 4 +- .../schema/diffing/SchemaDiffingTest.groovy | 28 +++++ 7 files changed, 204 insertions(+), 126 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java index a996b06ca7..cf2e3f9747 100644 --- a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java @@ -39,7 +39,7 @@ public void analyzeEdits(List editOperations) { } String fieldName = deletedVertex.getProperty("name"); // find the "dummy-type" vertex for this field - Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraphFactory.DUMMY_TYPE_VERTEX))); + Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraph.DUMMY_TYPE_VERTEX))); Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 0c6f1b5921..751146d79b 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -19,20 +19,20 @@ import static graphql.schema.diffing.SchemaDiffing.diffNamedList; import static graphql.schema.diffing.SchemaDiffing.diffVertices; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; -import static graphql.schema.diffing.SchemaGraphFactory.ENUM; -import static graphql.schema.diffing.SchemaGraphFactory.ENUM_VALUE; -import static graphql.schema.diffing.SchemaGraphFactory.FIELD; -import static graphql.schema.diffing.SchemaGraphFactory.INPUT_FIELD; -import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; -import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; -import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; -import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; -import static graphql.schema.diffing.SchemaGraphFactory.UNION; +import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT; +import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; +import static graphql.schema.diffing.SchemaGraph.ARGUMENT; +import static graphql.schema.diffing.SchemaGraph.DIRECTIVE; +import static graphql.schema.diffing.SchemaGraph.DUMMY_TYPE_VERTEX; +import static graphql.schema.diffing.SchemaGraph.ENUM; +import static graphql.schema.diffing.SchemaGraph.ENUM_VALUE; +import static graphql.schema.diffing.SchemaGraph.FIELD; +import static graphql.schema.diffing.SchemaGraph.INPUT_FIELD; +import static graphql.schema.diffing.SchemaGraph.INPUT_OBJECT; +import static graphql.schema.diffing.SchemaGraph.INTERFACE; +import static graphql.schema.diffing.SchemaGraph.OBJECT; +import static graphql.schema.diffing.SchemaGraph.SCALAR; +import static graphql.schema.diffing.SchemaGraph.UNION; import static graphql.util.FpKit.concat; public class FillupIsolatedVertices { @@ -232,7 +232,46 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return APPLIED_ARGUMENT.equals(vertex.getType()) && !vertex.isBuiltInType(); } }; - List contexts = Arrays.asList(appliedArgument); + IsolatedVertexContext appliedDirective = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { + Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); + return appliedDirective.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + IsolatedVertexContext appliedDirectiveContainer = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { + Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); + Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + return appliedDirectiveContainer.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + IsolatedVertexContext parentOfAppliedDirectiveContainer = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { + Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); + Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + Vertex parent = schemaGraph.getParentSchemaElement(appliedDirectiveContainer); + return parent.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(appliedArgument, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective); return contexts; } @@ -378,50 +417,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } public abstract static class IsolatedVertexContext { -// // it is always by type first -// final String type; -// // then a list of names -// List subContextIds = new ArrayList<>(); -// -// public IsolatedVertexContext(String type) { -// this.type = type; -// } -// -// public static IsolatedVertexContext newContext(String type) { -// return new IsolatedVertexContext(type); -// } -// -// public static IsolatedVertexContext newContext(String type, String subContext1, String subContext2) { -// IsolatedVertexContext result = new IsolatedVertexContext(type); -// result.subContextIds.add(subContext1); -// result.subContextIds.add(subContext2); -// return result; -// } -// -// public static IsolatedVertexContext newContext(String type, String subContext1, String subContext2, String subContext3) { -// IsolatedVertexContext result = new IsolatedVertexContext(type); -// result.subContextIds.add(subContext1); -// result.subContextIds.add(subContext2); -// result.subContextIds.add(subContext3); -// return result; -// } -// -// public static IsolatedVertexContext newContext(String type, String subContext1) { -// IsolatedVertexContext result = new IsolatedVertexContext(type); -// result.subContextIds.add(subContext1); -// return result; -// } public abstract String idForVertex(Vertex vertex, SchemaGraph schemaGraph); public abstract boolean filter(Vertex vertex, SchemaGraph schemaGraph); } - /** - * This is all about which vertices are allowed to map to which isolated vertices. - * It maps a "context" to a list of isolated vertices. - * Contexts are "InputField", "foo.InputObject.InputField" - */ public class IsolatedVertices { public Multimap contextToIsolatedSourceVertices = HashMultimap.create(); @@ -434,20 +435,11 @@ public class IsolatedVertices { public final Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); -// public void putSource(Object contextId, Vertex v) { -// contextToIsolatedSourceVertices.put(contextId, v); -// -// } - public void putSource(Object contextId, Collection isolatedSourcedVertices) { contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); allIsolatedSource.addAll(isolatedSourcedVertices); } -// public void putTarget(Object contextId, Vertex v) { -// contextToIsolatedTargetVertices.put(contextId, v); -// } - public void putTarget(Object contextId, Collection isolatedTargetVertices) { contextToIsolatedTargetVertices.putAll(contextId, isolatedTargetVertices); allIsolatedTarget.addAll(isolatedTargetVertices); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 088e7e2c78..b099744b98 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -32,20 +32,20 @@ import java.util.function.Function; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.DUMMY_TYPE_VERTEX; -import static graphql.schema.diffing.SchemaGraphFactory.ENUM; -import static graphql.schema.diffing.SchemaGraphFactory.ENUM_VALUE; -import static graphql.schema.diffing.SchemaGraphFactory.FIELD; -import static graphql.schema.diffing.SchemaGraphFactory.INPUT_FIELD; -import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; -import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; -import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; -import static graphql.schema.diffing.SchemaGraphFactory.SCALAR; -import static graphql.schema.diffing.SchemaGraphFactory.UNION; +import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT; +import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; +import static graphql.schema.diffing.SchemaGraph.ARGUMENT; +import static graphql.schema.diffing.SchemaGraph.DIRECTIVE; +import static graphql.schema.diffing.SchemaGraph.DUMMY_TYPE_VERTEX; +import static graphql.schema.diffing.SchemaGraph.ENUM; +import static graphql.schema.diffing.SchemaGraph.ENUM_VALUE; +import static graphql.schema.diffing.SchemaGraph.FIELD; +import static graphql.schema.diffing.SchemaGraph.INPUT_FIELD; +import static graphql.schema.diffing.SchemaGraph.INPUT_OBJECT; +import static graphql.schema.diffing.SchemaGraph.INTERFACE; +import static graphql.schema.diffing.SchemaGraph.OBJECT; +import static graphql.schema.diffing.SchemaGraph.SCALAR; +import static graphql.schema.diffing.SchemaGraph.UNION; import static java.lang.String.format; import static java.util.Collections.synchronizedMap; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 905d78500c..1829836694 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -5,9 +5,11 @@ import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Table; +import graphql.Assert; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -15,17 +17,47 @@ import java.util.Optional; import java.util.function.Predicate; +import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.SchemaGraphFactory.APPLIED_ARGUMENT; -import static graphql.schema.diffing.SchemaGraphFactory.DIRECTIVE; -import static graphql.schema.diffing.SchemaGraphFactory.FIELD; -import static graphql.schema.diffing.SchemaGraphFactory.INPUT_OBJECT; -import static graphql.schema.diffing.SchemaGraphFactory.INTERFACE; -import static graphql.schema.diffing.SchemaGraphFactory.OBJECT; import static java.lang.String.format; +import static java.lang.String.valueOf; public class SchemaGraph { + public static final String OBJECT = "Object"; + public static final String INTERFACE = "Interface"; + public static final String UNION = "Union"; + public static final String FIELD = "Field"; + public static final String ARGUMENT = "Argument"; + public static final String SCALAR = "Scalar"; + public static final String ENUM = "Enum"; + public static final String ENUM_VALUE = "EnumValue"; + public static final String INPUT_OBJECT = "InputObject"; + public static final String INPUT_FIELD = "InputField"; + public static final String DIRECTIVE = "Directive"; + public static final String APPLIED_DIRECTIVE = "AppliedDirective"; + public static final String APPLIED_ARGUMENT = "AppliedArgument"; + public static final String DUMMY_TYPE_VERTEX = "__DUMMY_TYPE_VERTEX"; + public static final String ISOLATED = "__ISOLATED"; + + public static final List ALL_TYPES = Arrays.asList(DUMMY_TYPE_VERTEX, OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM, ENUM_VALUE, APPLIED_DIRECTIVE, FIELD, ARGUMENT, APPLIED_ARGUMENT, DIRECTIVE, INPUT_FIELD); + public static final List ALL_NAMED_TYPES = Arrays.asList(OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM); + + /** + * SCHEMA, + * SCALAR, + * OBJECT, + * FIELD_DEFINITION, + * ARGUMENT_DEFINITION, + * INTERFACE, + * UNION, + * ENUM, + * ENUM_VALUE, + * INPUT_OBJECT, + * INPUT_FIELD_DEFINITION + */ + public static final List appliedDirectiveContainerTypes = Arrays.asList(SCALAR, OBJECT, FIELD, ARGUMENT, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD); + private List vertices = new ArrayList<>(); private List edges = new ArrayList<>(); @@ -169,5 +201,52 @@ public Vertex getInputObjectForInputField(Vertex inputField) { return adjacentVertices.get(0); } + public Vertex getAppliedDirectiveForAppliedArgument(Vertex appliedArgument) { + List adjacentVertices = this.getAdjacentVertices(appliedArgument, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE)); + assertTrue(adjacentVertices.size() == 1, () -> format("No applied directive found for %s", appliedArgument)); + return adjacentVertices.get(0); + } + + public Vertex getAppliedDirectiveContainerForAppliedDirective(Vertex appliedDirective) { + List adjacentVertices = this.getAdjacentVertices(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); + assertTrue(adjacentVertices.size() == 1, () -> format("No applied directive container found for %s", appliedDirective)); + return adjacentVertices.get(0); + } + + public Vertex getParentSchemaElement(Vertex vertex) { + switch (vertex.getType()) { + case OBJECT: + break; + case INTERFACE: + break; + case UNION: + break; + case FIELD: + return getFieldsContainerForField(vertex); + case ARGUMENT: + return getFieldOrDirectiveForArgument(vertex); + case SCALAR: + break; + case ENUM: + break; + case ENUM_VALUE: + break; + case INPUT_OBJECT: + break; + case INPUT_FIELD: + return getInputObjectForInputField(vertex); + case DIRECTIVE: + break; + case APPLIED_DIRECTIVE: + break; + case APPLIED_ARGUMENT: + break; + case DUMMY_TYPE_VERTEX: + break; + case ISOLATED: + return Assert.assertShouldNeverHappen(); + } + return assertShouldNeverHappen(); + } } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index b5a78e53a8..58d4275ec2 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -12,7 +12,6 @@ import graphql.util.TraverserVisitor; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -21,26 +20,6 @@ public class SchemaGraphFactory { - public static final String ISOLATED = "__ISOLATED"; - - public static final String DUMMY_TYPE_VERTEX = "__DUMMY_TYPE_VERTEX"; - public static final String OBJECT = "Object"; - public static final String INTERFACE = "Interface"; - public static final String UNION = "Union"; - public static final String INPUT_OBJECT = "InputObject"; - public static final String SCALAR = "Scalar"; - public static final String ENUM = "Enum"; - public static final String ENUM_VALUE = "EnumValue"; - public static final String APPLIED_DIRECTIVE = "AppliedDirective"; - public static final String FIELD = "Field"; - public static final String ARGUMENT = "Argument"; - public static final String APPLIED_ARGUMENT = "AppliedArgument"; - public static final String DIRECTIVE = "Directive"; - public static final String INPUT_FIELD = "InputField"; - - public static final List ALL_TYPES = Arrays.asList(DUMMY_TYPE_VERTEX, OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM, ENUM_VALUE, APPLIED_DIRECTIVE, FIELD, ARGUMENT, APPLIED_ARGUMENT, DIRECTIVE, INPUT_FIELD); - public static final List ALL_NAMED_TYPES = Arrays.asList(OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM); - private int counter = 1; private final String debugPrefix; @@ -117,19 +96,19 @@ public TraversalControl leave(TraverserContext context) { ArrayList copyOfVertices = new ArrayList<>(schemaGraph.getVertices()); for (Vertex vertex : copyOfVertices) { - if (OBJECT.equals(vertex.getType())) { + if (SchemaGraph.OBJECT.equals(vertex.getType())) { handleObjectVertex(vertex, schemaGraph, schema); } - if (INTERFACE.equals(vertex.getType())) { + if (SchemaGraph.INTERFACE.equals(vertex.getType())) { handleInterfaceVertex(vertex, schemaGraph, schema); } - if (UNION.equals(vertex.getType())) { + if (SchemaGraph.UNION.equals(vertex.getType())) { handleUnion(vertex, schemaGraph, schema); } - if (INPUT_OBJECT.equals(vertex.getType())) { + if (SchemaGraph.INPUT_OBJECT.equals(vertex.getType())) { handleInputObject(vertex, schemaGraph, schema); } - if (APPLIED_DIRECTIVE.equals(vertex.getType())) { + if (SchemaGraph.APPLIED_DIRECTIVE.equals(vertex.getType())) { handleAppliedDirective(vertex, schemaGraph, schema); } } @@ -155,7 +134,7 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); @@ -211,7 +190,7 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); + Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); schemaGraph.addVertex(dummyTypeVertex); schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); @@ -237,7 +216,7 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume } private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex objectVertex = new Vertex(OBJECT, debugPrefix + String.valueOf(counter++)); + Vertex objectVertex = new Vertex(SchemaGraph.OBJECT, debugPrefix + String.valueOf(counter++)); objectVertex.setBuiltInType(isIntrospectionNode); objectVertex.add("name", graphQLObjectType.getName()); objectVertex.add("description", desc(graphQLObjectType.getDescription())); @@ -252,7 +231,7 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr } private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex fieldVertex = new Vertex(FIELD, debugPrefix + String.valueOf(counter++)); + Vertex fieldVertex = new Vertex(SchemaGraph.FIELD, debugPrefix + String.valueOf(counter++)); fieldVertex.setBuiltInType(isIntrospectionNode); fieldVertex.add("name", graphQLFieldDefinition.getName()); fieldVertex.add("description", desc(graphQLFieldDefinition.getDescription())); @@ -266,7 +245,7 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra } private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex(ARGUMENT, debugPrefix + String.valueOf(counter++)); + Vertex vertex = new Vertex(SchemaGraph.ARGUMENT, debugPrefix + String.valueOf(counter++)); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); vertex.add("description", desc(graphQLArgument.getDescription())); @@ -278,7 +257,7 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr } private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex scalarVertex = new Vertex(SCALAR, debugPrefix + String.valueOf(counter++)); + Vertex scalarVertex = new Vertex(SchemaGraph.SCALAR, debugPrefix + String.valueOf(counter++)); scalarVertex.setBuiltInType(isIntrospectionNode); if (ScalarInfo.isGraphqlSpecifiedScalar(scalarType.getName())) { scalarVertex.setBuiltInType(true); @@ -292,7 +271,7 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, bo } private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex interfaceVertex = new Vertex(INTERFACE, debugPrefix + String.valueOf(counter++)); + Vertex interfaceVertex = new Vertex(SchemaGraph.INTERFACE, debugPrefix + String.valueOf(counter++)); interfaceVertex.setBuiltInType(isIntrospectionNode); interfaceVertex.add("name", interfaceType.getName()); interfaceVertex.add("description", desc(interfaceType.getDescription())); @@ -307,12 +286,12 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema } private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex enumVertex = new Vertex(ENUM, debugPrefix + String.valueOf(counter++)); + Vertex enumVertex = new Vertex(SchemaGraph.ENUM, debugPrefix + String.valueOf(counter++)); enumVertex.setBuiltInType(isIntrospectionNode); enumVertex.add("name", enumType.getName()); enumVertex.add("description", desc(enumType.getDescription())); for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) { - Vertex enumValueVertex = new Vertex(ENUM_VALUE, debugPrefix + String.valueOf(counter++)); + Vertex enumValueVertex = new Vertex(SchemaGraph.ENUM_VALUE, debugPrefix + String.valueOf(counter++)); enumValueVertex.setBuiltInType(isIntrospectionNode); enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); @@ -325,7 +304,7 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean } private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex unionVertex = new Vertex(UNION, debugPrefix + String.valueOf(counter++)); + Vertex unionVertex = new Vertex(SchemaGraph.UNION, debugPrefix + String.valueOf(counter++)); unionVertex.setBuiltInType(isIntrospectionNode); unionVertex.add("name", unionType.getName()); unionVertex.add("description", desc(unionType.getDescription())); @@ -335,7 +314,7 @@ private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boole } private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex inputObjectVertex = new Vertex(INPUT_OBJECT, debugPrefix + String.valueOf(counter++)); + Vertex inputObjectVertex = new Vertex(SchemaGraph.INPUT_OBJECT, debugPrefix + String.valueOf(counter++)); inputObjectVertex.setBuiltInType(isIntrospectionNode); inputObjectVertex.add("name", inputObject.getName()); inputObjectVertex.add("description", desc(inputObject.getDescription())); @@ -352,11 +331,11 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph schemaGraph) { for (GraphQLDirective appliedDirective : appliedDirectives) { - Vertex appliedDirectiveVertex = new Vertex(APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); + Vertex appliedDirectiveVertex = new Vertex(SchemaGraph.APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) { if (appliedArgument.hasSetValue()) { - Vertex appliedArgumentVertex = new Vertex(APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); + Vertex appliedArgumentVertex = new Vertex(SchemaGraph.APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++)); appliedArgumentVertex.add("name", appliedArgument.getName()); appliedArgumentVertex.add("value", AstPrinter.printAst(ValuesResolver.valueToLiteral(appliedArgument.getArgumentValue(), appliedArgument.getType()))); schemaGraph.addVertex(appliedArgumentVertex); @@ -369,7 +348,7 @@ private void cratedAppliedDirectives(Vertex from, List applied } private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { - Vertex directiveVertex = new Vertex(DIRECTIVE, debugPrefix + String.valueOf(counter++)); + Vertex directiveVertex = new Vertex(SchemaGraph.DIRECTIVE, debugPrefix + String.valueOf(counter++)); directiveVertex.add("name", directive.getName()); directiveVertex.add("repeatable", directive.isRepeatable()); directiveVertex.add("locations", directive.validLocations()); @@ -386,7 +365,7 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) { } private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) { - Vertex vertex = new Vertex(INPUT_FIELD, debugPrefix + String.valueOf(counter++)); + Vertex vertex = new Vertex(SchemaGraph.INPUT_FIELD, debugPrefix + String.valueOf(counter++)); schemaGraph.addVertex(vertex); vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 386f31a5fe..e8917463cc 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -19,7 +19,7 @@ public class Vertex { private boolean builtInType; public static Vertex newIsolatedNode(String debugName) { - Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName); + Vertex vertex = new Vertex(SchemaGraph.ISOLATED, debugName); vertex.isolated = true; return vertex; } @@ -27,7 +27,7 @@ public static Vertex newIsolatedNode(String debugName) { public static Set newIsolatedNodes(int count, String debugName) { Set result = new LinkedHashSet<>(); for (int i = 1; i <= count; i++) { - Vertex vertex = new Vertex(SchemaGraphFactory.ISOLATED, debugName + i); + Vertex vertex = new Vertex(SchemaGraph.ISOLATED, debugName + i); vertex.isolated = true; result.add(vertex); } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index e087273d26..f8c738ce95 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1046,6 +1046,34 @@ class SchemaDiffingTest extends Specification { operations.size() == 2 } + def "applied arguments in different contexts"() { + given: + def schema1 = schema(''' + directive @d(a1: String, a2: String, b1: String, b2: String, b3: String, b4: String) on FIELD_DEFINITION + type Query { + foo: String @d(a1: "a1", a2: "a2") + foo2: String @d(b1: "b1", b2: "b2") + } + ''') + def schema2 = schema(""" + directive @d(a1: String, a2: String, b1: String, b2: String, b3: String, b4: String) on FIELD_DEFINITION + type Query { + foo: String @d(a1: "a1") + foo2: String @d(b2: "b2", b3: "b3", b4: "b4") + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + /** + * The test here is that the context of the applied argument is considered and that a2 is deleted and one b is inserted and another one changed. + */ + operations.size() == 5 + } + def "with directives"() { given: def schema1 = schema(''' From 9f7370349fdbad1edf6178d6a79a3fffcf4598fe Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 10 Feb 2022 17:07:35 +1100 Subject: [PATCH 049/294] isolated handling --- .../diffing/FillupIsolatedVertices.java | 7 ------ .../schema/diffing/SchemaDiffingTest.groovy | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 751146d79b..abb3576863 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -562,13 +562,6 @@ private void calcImpl( } } } else { - - ArrayList deletedVertices = new ArrayList<>(); - ArrayList insertedVertices = new ArrayList<>(); - HashBiMap sameVertices = HashBiMap.create(); - diffVertices(notUsedSource, notUsedTarget, deletedVertices, insertedVertices, sameVertices, vertex -> { - return concat(currentContextId, vertex.getName()); - }); usedSourceVertices.addAll(sourceGroups.get(sameContext)); usedTargetVertices.addAll(targetGroups.get(sameContext)); if (notUsedSource.size() > notUsedTarget.size()) { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index f8c738ce95..9fedc07d1e 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -968,6 +968,29 @@ class SchemaDiffingTest extends Specification { operations.size() == 11 } + def "same arguments in different contexts"() { + given: + def schema1 = schema(""" + type Query { + foo(someArg:String): String + } + """) + def schema2 = schema(""" + type Query { + field1(arg1: String): String + field2(arg1: String): String + field3(arg1: String): String + } + """) + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 18 + } + def "adding enum value"() { given: def schema1 = schema(""" From c54cb33de7f704959993cfb1147dfee10e5ee48d Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Feb 2022 18:26:08 +1100 Subject: [PATCH 050/294] sorting work --- .../diffing/FillupIsolatedVertices.java | 2 - .../graphql/schema/diffing/SchemaDiffing.java | 61 ++++++++++++++----- .../java/graphql/schema/diffing/Vertex.java | 32 ++++++++++ .../schema/diffing/SchemaDiffingTest.groovy | 23 +++++++ 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index abb3576863..1ab8e627ed 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -384,8 +384,6 @@ public void ensureGraphAreSameSize() { } else if (sourceGraph.size() > targetGraph.size()) { isolatedVertices.isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-isolated-builtin-")); } - - System.out.println(isolatedVertices); } private static List inputFieldContexts() { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index b099744b98..a1b8ef7fa0 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -234,30 +234,55 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Map vertexWeights = new LinkedHashMap<>(); - Map edgesWeights = new LinkedHashMap<>(); + + + // how often does each source edge (based on the label) appear in target graph + Map targetLabelCount = new LinkedHashMap<>(); + for (Edge targetEdge : targetGraph.getEdges()) { + targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); + } + // how often does each source vertex (based on the data) appear in the target graph + Map targetVertexDataCount = new LinkedHashMap<>(); + for (Vertex targetVertex : targetGraph.getVertices()) { + targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); + } + + // an infrequency weight is 1 - count in target. Meaning the higher the + // value, the smaller the count, the less frequent it. + // Higher Infrequency => more unique is the vertex/label + Map vertexInfrequencyWeights = new LinkedHashMap<>(); + Map edgesInfrequencyWeights = new LinkedHashMap<>(); for (Vertex vertex : sourceGraph.getVertices()) { - vertexWeights.put(vertex, infrequencyWeightForVertex(vertex, targetGraph)); + vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); } for (Edge edge : sourceGraph.getEdges()) { - edgesWeights.put(edge, infrequencyWeightForEdge(edge, targetGraph)); + edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); } - List result = new ArrayList<>(); + + /** + * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness + * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. + */ + + //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); - nextCandidates.sort(Comparator.comparingInt(o -> totalWeightWithAdjacentEdges(sourceGraph, o, vertexWeights, edgesWeights))); -// System.out.println("0: " + totalWeight(sourceGraph, nextCandidates.get(0), vertexWeights, edgesWeights)); -// System.out.println("last: " + totalWeight(sourceGraph, nextCandidates.get(nextCandidates.size() - 1), vertexWeights, edgesWeights)); + nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); + Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); - result.add(curVertex); nextCandidates.remove(nextCandidates.size() - 1); + List result = new ArrayList<>(); + result.add(curVertex); while (nextCandidates.size() > 0) { Vertex nextOne = null; int curMaxWeight = Integer.MIN_VALUE; int index = 0; int nextOneIndex = -1; + + // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices for (Vertex candidate : nextCandidates) { - int totalWeight = totalWeightWithSomeEdges(sourceGraph, candidate, allAdjacentEdges(sourceGraph, result, candidate), vertexWeights, edgesWeights); + List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); + int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); if (totalWeight > curMaxWeight) { nextOne = candidate; nextOneIndex = index; @@ -283,19 +308,23 @@ private List allAdjacentEdges(SchemaGraph schemaGraph, List fromLi return result; } - private int totalWeightWithSomeEdges(SchemaGraph sourceGraph, Vertex - vertex, List edges, Map vertexWeights, Map edgesWeights) { + private int totalInfrequencyWeightWithSomeEdges(Vertex vertex, + List edges, + Map vertexInfrequencyWeights, + Map edgesInfrequencyWeights) { if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } if (vertex.isIsolated()) { return Integer.MIN_VALUE + 2; } - return vertexWeights.get(vertex) + edges.stream().mapToInt(edgesWeights::get).sum(); + return vertexInfrequencyWeights.get(vertex) + edges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); } - private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex - vertex, Map vertexWeights, Map edgesWeights) { + private int totalInfrequencyWeightWithAdjacentEdges(SchemaGraph sourceGraph, + Vertex vertex, + Map vertexInfrequencyWeights, + Map edgesInfrequencyWeights) { if (vertex.isBuiltInType()) { return Integer.MIN_VALUE + 1; } @@ -303,7 +332,7 @@ private int totalWeightWithAdjacentEdges(SchemaGraph sourceGraph, Vertex return Integer.MIN_VALUE + 2; } List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); - return vertexWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesWeights::get).sum(); + return vertexInfrequencyWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); } private int infrequencyWeightForVertex(Vertex sourceVertex, SchemaGraph targetGraph) { diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index e8917463cc..42d88aef55 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -6,6 +6,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.Set; public class Vertex { @@ -95,4 +96,35 @@ public String toString() { '}'; } + public VertexData toData() { + return new VertexData(this.type, this.properties); + } + + public static class VertexData { + private final String type; + private final Map properties; + + public VertexData(String type, Map properties) { + this.type = type; + this.properties = properties; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VertexData that = (VertexData) o; + return Objects.equals(type, that.type) && Objects.equals(properties, that.properties); + } + + @Override + public int hashCode() { + return Objects.hash(type, properties); + } + } + } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 9fedc07d1e..903197a369 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -363,6 +363,7 @@ class SchemaDiffingTest extends Specification { } + @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) @@ -384,6 +385,28 @@ class SchemaDiffingTest extends Specification { diff.size() == 171 } + @Ignore + def "change large schema a bit 2"() { + given: + def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) + int counter = 0; + def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { + @Override + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { + if (fieldDefinition.getName() == "field50") { + counter++; + return deleteNode(context); + } + return TraversalControl.CONTINUE + } + }) + println "deleted fields: " + counter + when: + def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) + then: + diff.size() == 171 + } + def "change object type name used twice"() { given: def schema1 = schema(""" From 836288cd27086ca971dc41635c82ff46aed4ed12 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 14 Feb 2022 18:42:38 +1100 Subject: [PATCH 051/294] reworked possible mapping handling --- .../diffing/FillupIsolatedVertices.java | 529 +++++++++++++----- .../graphql/schema/diffing/SchemaDiffing.java | 169 +++--- 2 files changed, 484 insertions(+), 214 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 1ab8e627ed..7d841c6e04 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -1,6 +1,5 @@ package graphql.schema.diffing; -import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; @@ -18,7 +17,6 @@ import java.util.Set; import static graphql.schema.diffing.SchemaDiffing.diffNamedList; -import static graphql.schema.diffing.SchemaDiffing.diffVertices; import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT; import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; import static graphql.schema.diffing.SchemaGraph.ARGUMENT; @@ -60,6 +58,46 @@ public class FillupIsolatedVertices { typeContexts.put(DIRECTIVE, directiveContext()); } + private static List inputFieldContexts() { + IsolatedVertexContext inputFieldType = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return INPUT_FIELD.equals(vertex.getType()); + } + }; + IsolatedVertexContext inputObjectContext = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) { + Vertex inputObject = schemaGraph.getInputObjectForInputField(inputField); + return inputObject.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + IsolatedVertexContext inputFieldName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) { + return inputField.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(inputFieldType, inputObjectContext, inputFieldName); + return contexts; + } + + private static List dummyTypeContext() { IsolatedVertexContext dummyType = new IsolatedVertexContext() { @Override @@ -69,7 +107,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return DUMMY_TYPE_VERTEX.equals(vertex.getType()) && !vertex.isBuiltInType(); + return DUMMY_TYPE_VERTEX.equals(vertex.getType()); } }; List contexts = Arrays.asList(dummyType); @@ -85,7 +123,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return SCALAR.equals(vertex.getType()) && !vertex.isBuiltInType(); + return SCALAR.equals(vertex.getType()); } }; List contexts = Arrays.asList(scalar); @@ -101,7 +139,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return INPUT_OBJECT.equals(vertex.getType()) && !vertex.isBuiltInType(); + return INPUT_OBJECT.equals(vertex.getType()); } }; List contexts = Arrays.asList(inputObject); @@ -109,7 +147,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } private static List objectContext() { - IsolatedVertexContext object = new IsolatedVertexContext() { + IsolatedVertexContext objectType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -117,15 +155,27 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return OBJECT.equals(vertex.getType()) && !vertex.isBuiltInType(); + return OBJECT.equals(vertex.getType()); + } + }; + + IsolatedVertexContext objectName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex object, SchemaGraph schemaGraph) { + return object.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; } }; - List contexts = Arrays.asList(object); + List contexts = Arrays.asList(objectType, objectName); return contexts; } private static List enumContext() { - IsolatedVertexContext enumCtx = new IsolatedVertexContext() { + IsolatedVertexContext enumCtxType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -133,15 +183,26 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return ENUM.equals(vertex.getType()) && !vertex.isBuiltInType(); + return ENUM.equals(vertex.getType()); + } + }; + IsolatedVertexContext enumName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex enumVertex, SchemaGraph schemaGraph) { + return enumVertex.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; } }; - List contexts = Arrays.asList(enumCtx); + List contexts = Arrays.asList(enumCtxType, enumName); return contexts; } private static List enumValueContext() { - IsolatedVertexContext enumValue = new IsolatedVertexContext() { + IsolatedVertexContext enumValueType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -149,15 +210,26 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return ENUM_VALUE.equals(vertex.getType()) && !vertex.isBuiltInType(); + return ENUM_VALUE.equals(vertex.getType()); + } + }; + IsolatedVertexContext enumValueName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) { + return enumValue.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; } }; - List contexts = Arrays.asList(enumValue); + List contexts = Arrays.asList(enumValueType, enumValueName); return contexts; } private static List interfaceContext() { - IsolatedVertexContext interfaceContext = new IsolatedVertexContext() { + IsolatedVertexContext interfaceType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -165,15 +237,27 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return INTERFACE.equals(vertex.getType()) && !vertex.isBuiltInType(); + return INTERFACE.equals(vertex.getType()); } }; - List contexts = Arrays.asList(interfaceContext); + IsolatedVertexContext interfaceName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex interfaceVertex, SchemaGraph schemaGraph) { + return interfaceVertex.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + List contexts = Arrays.asList(interfaceType, interfaceName); return contexts; } private static List unionContext() { - IsolatedVertexContext unionContext = new IsolatedVertexContext() { + IsolatedVertexContext unionType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -181,15 +265,27 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return UNION.equals(vertex.getType()) && !vertex.isBuiltInType(); + return UNION.equals(vertex.getType()); } }; - List contexts = Arrays.asList(unionContext); + IsolatedVertexContext unionName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex union, SchemaGraph schemaGraph) { + return union.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + List contexts = Arrays.asList(unionType, unionName); return contexts; } private static List directiveContext() { - IsolatedVertexContext directiveContext = new IsolatedVertexContext() { + IsolatedVertexContext directiveType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -197,15 +293,27 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return DIRECTIVE.equals(vertex.getType()) && !vertex.isBuiltInType(); + return DIRECTIVE.equals(vertex.getType()); + } + }; + IsolatedVertexContext directiveName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex directive, SchemaGraph schemaGraph) { + return directive.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; } }; - List contexts = Arrays.asList(directiveContext); + + List contexts = Arrays.asList(directiveType); return contexts; } private static List appliedDirectiveContext() { - IsolatedVertexContext appliedDirective = new IsolatedVertexContext() { + IsolatedVertexContext appliedDirectiveType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -213,15 +321,27 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return APPLIED_DIRECTIVE.equals(vertex.getType()) && !vertex.isBuiltInType(); + return APPLIED_DIRECTIVE.equals(vertex.getType()); } }; - List contexts = Arrays.asList(appliedDirective); + IsolatedVertexContext appliedDirectiveName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { + return appliedDirective.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + List contexts = Arrays.asList(appliedDirectiveType, appliedDirectiveName); return contexts; } private static List appliedArgumentContext() { - IsolatedVertexContext appliedArgument = new IsolatedVertexContext() { + IsolatedVertexContext appliedArgumentType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -229,7 +349,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return APPLIED_ARGUMENT.equals(vertex.getType()) && !vertex.isBuiltInType(); + return APPLIED_ARGUMENT.equals(vertex.getType()); } }; IsolatedVertexContext appliedDirective = new IsolatedVertexContext() { @@ -271,7 +391,18 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(appliedArgument, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective); + IsolatedVertexContext appliedArgumentName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { + return appliedArgument.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(appliedArgumentType, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective, appliedArgumentName); return contexts; } @@ -284,7 +415,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return FIELD.equals(vertex.getType()) && !vertex.isBuiltInType(); + return FIELD.equals(vertex.getType()); } }; IsolatedVertexContext container = new IsolatedVertexContext() { @@ -299,13 +430,25 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(field, container); + + IsolatedVertexContext fieldName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex field, SchemaGraph schemaGraph) { + return field.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(field, container, fieldName); return contexts; } private static List argumentsForFieldsContexts() { - IsolatedVertexContext argument = new IsolatedVertexContext() { + IsolatedVertexContext argumentType = new IsolatedVertexContext() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -313,7 +456,7 @@ public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @Override public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return ARGUMENT.equals(vertex.getType()) && !vertex.isBuiltInType(); + return ARGUMENT.equals(vertex.getType()); } }; @@ -348,7 +491,18 @@ public boolean filter(Vertex argument, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(argument, containerOrDirectiveHolder, fieldOrDirective); + IsolatedVertexContext argumentName = new IsolatedVertexContext() { + @Override + public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { + return argument.getName(); + } + + @Override + public boolean filter(Vertex argument, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(argumentType, containerOrDirectiveHolder, fieldOrDirective, argumentName); return contexts; } @@ -360,59 +514,35 @@ public FillupIsolatedVertices(SchemaGraph sourceGraph, SchemaGraph targetGraph) } public void ensureGraphAreSameSize() { - calcIsolatedVertices(typeContexts.get(FIELD), FIELD); - calcIsolatedVertices(typeContexts.get(ARGUMENT), ARGUMENT); - calcIsolatedVertices(typeContexts.get(INPUT_FIELD), INPUT_FIELD); - calcIsolatedVertices(typeContexts.get(DUMMY_TYPE_VERTEX), DUMMY_TYPE_VERTEX); - calcIsolatedVertices(typeContexts.get(OBJECT), OBJECT); - calcIsolatedVertices(typeContexts.get(INTERFACE), INTERFACE); - calcIsolatedVertices(typeContexts.get(UNION), UNION); - calcIsolatedVertices(typeContexts.get(INPUT_OBJECT), INPUT_OBJECT); - calcIsolatedVertices(typeContexts.get(SCALAR), SCALAR); - calcIsolatedVertices(typeContexts.get(ENUM), ENUM); - calcIsolatedVertices(typeContexts.get(ENUM_VALUE), ENUM_VALUE); - calcIsolatedVertices(typeContexts.get(APPLIED_DIRECTIVE), APPLIED_DIRECTIVE); - calcIsolatedVertices(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT); - calcIsolatedVertices(typeContexts.get(DIRECTIVE), DIRECTIVE); +// calcIsolatedVertices(typeContexts.get(FIELD), FIELD); + calcPossibleMappings(typeContexts.get(FIELD), FIELD); + calcPossibleMappings(typeContexts.get(ARGUMENT), ARGUMENT); + calcPossibleMappings(typeContexts.get(INPUT_FIELD), INPUT_FIELD); + calcPossibleMappings(typeContexts.get(DUMMY_TYPE_VERTEX), DUMMY_TYPE_VERTEX); + calcPossibleMappings(typeContexts.get(OBJECT), OBJECT); + calcPossibleMappings(typeContexts.get(INTERFACE), INTERFACE); + calcPossibleMappings(typeContexts.get(UNION), UNION); + calcPossibleMappings(typeContexts.get(INPUT_OBJECT), INPUT_OBJECT); + calcPossibleMappings(typeContexts.get(SCALAR), SCALAR); + calcPossibleMappings(typeContexts.get(ENUM), ENUM); + calcPossibleMappings(typeContexts.get(ENUM_VALUE), ENUM_VALUE); + calcPossibleMappings(typeContexts.get(APPLIED_DIRECTIVE), APPLIED_DIRECTIVE); + calcPossibleMappings(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT); + calcPossibleMappings(typeContexts.get(DIRECTIVE), DIRECTIVE); sourceGraph.addVertices(isolatedVertices.allIsolatedSource); targetGraph.addVertices(isolatedVertices.allIsolatedTarget); - if (sourceGraph.size() < targetGraph.size()) { - isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); - } else if (sourceGraph.size() > targetGraph.size()) { - isolatedVertices.isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-isolated-builtin-")); - } + System.out.println("done isolated"); + Assert.assertTrue(sourceGraph.size() == targetGraph.size()); +// if (sourceGraph.size() < targetGraph.size()) { +// isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); +// } else if (sourceGraph.size() > targetGraph.size()) { +// isolatedVertices.isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-isolated-builtin-")); +// } } - private static List inputFieldContexts() { - IsolatedVertexContext inputFieldContext = new IsolatedVertexContext() { - @Override - public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { - return vertex.getType(); - } - - @Override - public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return INPUT_FIELD.equals(vertex.getType()) && !vertex.isBuiltInType(); - } - }; - IsolatedVertexContext inputObjectContext = new IsolatedVertexContext() { - @Override - public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { - Vertex inputObject = schemaGraph.getInputObjectForInputField(vertex); - return inputObject.getName(); - } - - @Override - public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return true; - } - }; - List contexts = Arrays.asList(inputFieldContext, inputObjectContext); - return contexts; - } public abstract static class IsolatedVertexContext { @@ -432,6 +562,22 @@ public class IsolatedVertices { public final Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); public final Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); + // from source to target + public Multimap possibleMappings = HashMultimap.create(); + + public void putPossibleMappings(Collection sourceVertices, Collection targetVertex) { + for (Vertex sourceVertex : sourceVertices) { + possibleMappings.putAll(sourceVertex, targetVertex); + } + } + + public void addIsolatedSource(Collection isolatedSource) { + allIsolatedSource.addAll(isolatedSource); + } + + public void addIsolatedTarget(Collection isolatedTarget) { + allIsolatedTarget.addAll(isolatedTarget); + } public void putSource(Object contextId, Collection isolatedSourcedVertices) { contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); @@ -443,64 +589,75 @@ public void putTarget(Object contextId, Collection isolatedTargetVertice allIsolatedTarget.addAll(isolatedTargetVertices); } - public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Vertex targetVertex) { - List contexts = typeContexts.get(targetVertex.getType()); - Assert.assertNotNull(contexts); - List contextForVertex = new ArrayList<>(); - for (IsolatedVertexContext isolatedVertexContext : contexts) { - contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); - } - if (!targetVertex.getType().equals(DUMMY_TYPE_VERTEX)) { - contextForVertex.add(targetVertex.getName()); - } - while (contextForVertex.size() > 0) { - if (isolatedVertices.contextToIsolatedSourceVertices.containsKey(contextForVertex)) { - return isolatedVertices.contextToIsolatedSourceVertices.get(contextForVertex).contains(isolatedSourceVertex); - } - contextForVertex.remove(contextForVertex.size() - 1); - } - return false; + public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { + return possibleMappings.containsEntry(sourceVertex, targetVertex); } - public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isolatedTargetVertex) { - List contexts = typeContexts.get(sourceVertex.getType()); - Assert.assertNotNull(contexts); - List contextForVertex = new ArrayList<>(); - for (IsolatedVertexContext isolatedVertexContext : contexts) { - contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); - } - if (!sourceVertex.getType().equals(DUMMY_TYPE_VERTEX)) { - contextForVertex.add(sourceVertex.getName()); - } - while (contextForVertex.size() > 0) { - if (isolatedVertices.contextToIsolatedTargetVertices.containsKey(contextForVertex)) { - return isolatedVertices.contextToIsolatedTargetVertices.get(contextForVertex).contains(isolatedTargetVertex); - } - contextForVertex.remove(contextForVertex.size() - 1); - } - return false; - - } +// public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Vertex targetVertex) { +// List contexts = typeContexts.get(targetVertex.getType()); +// Assert.assertNotNull(contexts); +// List contextForVertex = new ArrayList<>(); +// for (IsolatedVertexContext isolatedVertexContext : contexts) { +// contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); +// } +// if (!targetVertex.getType().equals(DUMMY_TYPE_VERTEX)) { +// contextForVertex.add(targetVertex.getName()); +// } +// while (contextForVertex.size() > 0) { +// if (isolatedVertices.contextToIsolatedSourceVertices.containsKey(contextForVertex)) { +// return isolatedVertices.contextToIsolatedSourceVertices.get(contextForVertex).contains(isolatedSourceVertex); +// } +// contextForVertex.remove(contextForVertex.size() - 1); +// } +// return false; +// } +// +// public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isolatedTargetVertex) { +// List contexts = typeContexts.get(sourceVertex.getType()); +// Assert.assertNotNull(contexts); +// List contextForVertex = new ArrayList<>(); +// for (IsolatedVertexContext isolatedVertexContext : contexts) { +// contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); +// } +// if (!sourceVertex.getType().equals(DUMMY_TYPE_VERTEX)) { +// contextForVertex.add(sourceVertex.getName()); +// } +// while (contextForVertex.size() > 0) { +// if (isolatedVertices.contextToIsolatedTargetVertices.containsKey(contextForVertex)) { +// return isolatedVertices.contextToIsolatedTargetVertices.get(contextForVertex).contains(isolatedTargetVertex); +// } +// contextForVertex.remove(contextForVertex.size() - 1); +// } +// return false; +// +// } } public void calcIsolatedVertices(List contexts, String typeNameForDebug) { Collection currentSourceVertices = sourceGraph.getVertices(); Collection currentTargetVertices = targetGraph.getVertices(); - calcImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); + calcIsolatedVerticesImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); } - private void calcImpl( + /** + * + */ + private void calcIsolatedVerticesImpl( Collection currentSourceVertices, Collection currentTargetVertices, - List contextId, - int contextIx, - List contexts, + List currentContextId, + int curContextSegmentIx, + List contextSegments, Set usedSourceVertices, Set usedTargetVertices, String typeNameForDebug) { - IsolatedVertexContext finalCurrentContext = contexts.get(contextIx); + /** + * the elements grouped by the current context segment. + */ + + IsolatedVertexContext finalCurrentContext = contextSegments.get(curContextSegmentIx); Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, v -> finalCurrentContext.filter(v, sourceGraph), v -> finalCurrentContext.idForVertex(v, sourceGraph)); @@ -508,11 +665,10 @@ private void calcImpl( v -> finalCurrentContext.filter(v, targetGraph), v -> finalCurrentContext.idForVertex(v, targetGraph)); - List deletedContexts = new ArrayList<>(); - List insertedContexts = new ArrayList<>(); - List sameContexts = new ArrayList<>(); + // all of the relevant vertices will be handled + - if (contextIx == 0) { + if (curContextSegmentIx == 0) { if (sourceGroups.size() == 0 && targetGroups.size() == 1) { // we only have inserted elements String context = targetGroups.keySet().iterator().next(); @@ -527,13 +683,16 @@ private void calcImpl( isolatedVertices.putTarget(Arrays.asList(context), newTargetVertices); } } + List deletedContexts = new ArrayList<>(); + List insertedContexts = new ArrayList<>(); + List sameContexts = new ArrayList<>(); diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); for (String sameContext : sameContexts) { ImmutableList sourceVertices = sourceGroups.get(sameContext); ImmutableList targetVertices = targetGroups.get(sameContext); - List currentContextId = concat(contextId, sameContext); - if (contexts.size() > contextIx + 1) { - calcImpl(sourceVertices, targetVertices, currentContextId, contextIx + 1, contexts, usedSourceVertices, usedTargetVertices, typeNameForDebug); + List newContextId = concat(currentContextId, sameContext); + if (contextSegments.size() > curContextSegmentIx + 1) { + calcIsolatedVerticesImpl(sourceVertices, targetVertices, newContextId, curContextSegmentIx + 1, contextSegments, usedSourceVertices, usedTargetVertices, typeNameForDebug); } Set notUsedSource = new LinkedHashSet<>(sourceVertices); @@ -545,7 +704,7 @@ private void calcImpl( * We know that the first context is just by type and we have all the remaining vertices of the same * type here. */ - if (contextIx == 0) { + if (curContextSegmentIx == 0) { if (notUsedSource.size() > notUsedTarget.size()) { Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); // all deleted vertices can map to all new TargetVertices @@ -566,13 +725,13 @@ private void calcImpl( Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); // all deleted vertices can map to all new TargetVertices for (Vertex deletedVertex : notUsedSource) { - isolatedVertices.putTarget(concat(currentContextId, deletedVertex.getName()), newTargetVertices); + isolatedVertices.putTarget(concat(newContextId, deletedVertex.getName()), newTargetVertices); } } else if (notUsedTarget.size() > notUsedSource.size()) { Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); // all inserted fields can map to all new source vertices for (Vertex insertedVertex : notUsedTarget) { - isolatedVertices.putSource(concat(currentContextId, insertedVertex.getName()), newSourceVertices); + isolatedVertices.putSource(concat(newContextId, insertedVertex.getName()), newSourceVertices); } } } @@ -581,5 +740,111 @@ private void calcImpl( } + public void calcPossibleMappings(List contexts, String typeNameForDebug) { + Collection currentSourceVertices = sourceGraph.getVertices(); + Collection currentTargetVertices = targetGraph.getVertices(); + calcPossibleMappingImpl(currentSourceVertices, + currentTargetVertices, + Collections.emptyList(), + 0, + contexts, + new LinkedHashSet<>(), + new LinkedHashSet<>(), + typeNameForDebug); + } + + /** + * calc for the provided context + */ + private void calcPossibleMappingImpl( + Collection currentSourceVertices, + Collection currentTargetVertices, + List contextId, + int contextIx, + List contexts, + Set usedSourceVertices, + Set usedTargetVertices, + String typeNameForDebug) { + + IsolatedVertexContext finalCurrentContext = contexts.get(contextIx); + Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, + v -> finalCurrentContext.filter(v, sourceGraph), + v -> finalCurrentContext.idForVertex(v, sourceGraph)); + Map> targetGroups = FpKit.filterAndGroupingBy(currentTargetVertices, + v -> finalCurrentContext.filter(v, targetGraph), + v -> finalCurrentContext.idForVertex(v, targetGraph)); + + + List deletedContexts = new ArrayList<>(); + List insertedContexts = new ArrayList<>(); + List sameContexts = new ArrayList<>(); + diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); + + // for each unchanged context we descend recursively into + for (String sameContext : sameContexts) { + ImmutableList sourceVerticesInContext = sourceGroups.get(sameContext); + ImmutableList targetVerticesInContext = targetGroups.get(sameContext); + List currentContextId = concat(contextId, sameContext); + if (contexts.size() > contextIx + 1) { + calcPossibleMappingImpl(sourceVerticesInContext, targetVerticesInContext, currentContextId, contextIx + 1, contexts, usedSourceVertices, usedTargetVertices, typeNameForDebug); + } + /** + * Either there was no context segment left or not all vertices were relevant for + * Either way: fill up with isolated vertices and record as possible mapping + */ + Set notUsedSource = new LinkedHashSet<>(sourceVerticesInContext); + notUsedSource.removeAll(usedSourceVertices); + Set notUsedTarget = new LinkedHashSet<>(targetVerticesInContext); + notUsedTarget.removeAll(usedTargetVertices); + + if (notUsedSource.size() > notUsedTarget.size()) { + Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); + isolatedVertices.addIsolatedTarget(newTargetVertices); + notUsedTarget.addAll(newTargetVertices); + } else if (notUsedTarget.size() > notUsedSource.size()) { + Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); + isolatedVertices.addIsolatedSource(newSourceVertices); + notUsedSource.addAll(newSourceVertices); + } + isolatedVertices.putPossibleMappings(notUsedSource, notUsedTarget); + usedSourceVertices.addAll(notUsedSource); + usedTargetVertices.addAll(notUsedTarget); + } + + Set possibleTargetVertices = new LinkedHashSet<>(); + for (String insertedContext : insertedContexts) { + ImmutableList vertices = targetGroups.get(insertedContext); + for (Vertex targetVertex : vertices) { + if (!usedTargetVertices.contains(targetVertex)) { + possibleTargetVertices.add(targetVertex); + } + } + usedTargetVertices.addAll(vertices); + } + + Set possibleSourceVertices = new LinkedHashSet<>(); + for (String deletedContext : deletedContexts) { + ImmutableList vertices = sourceGroups.get(deletedContext); + for (Vertex sourceVertex : vertices) { + if (!usedSourceVertices.contains(sourceVertex)) { + possibleSourceVertices.add(sourceVertex); + } + } + usedSourceVertices.addAll(vertices); + } + + if (possibleSourceVertices.size() > possibleTargetVertices.size()) { + Set newTargetVertices = Vertex.newIsolatedNodes(possibleSourceVertices.size() - possibleTargetVertices.size(), "target-isolated-" + typeNameForDebug + "-"); + isolatedVertices.addIsolatedTarget(newTargetVertices); + possibleTargetVertices.addAll(newTargetVertices); + } else if (possibleTargetVertices.size() > possibleSourceVertices.size()) { + Set newSourceVertices = Vertex.newIsolatedNodes(possibleTargetVertices.size() - possibleSourceVertices.size(), "source-isolated-" + typeNameForDebug + "-"); + isolatedVertices.addIsolatedSource(newSourceVertices); + possibleSourceVertices.addAll(newSourceVertices); + } + isolatedVertices.putPossibleMappings(possibleSourceVertices, possibleTargetVertices); + + } + } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index a1b8ef7fa0..a853f48e77 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -95,10 +95,10 @@ public static void diffNamedVertices(Collection sourceVertices, List deleted, // sourceVertices List inserted, // targetVertices BiMap same) { - Map sourceByName = FpKit.groupingByUniqueKey(sourceVertices, vertex -> vertex.get("name")); - Map targetByName = FpKit.groupingByUniqueKey(targetVertices, vertex -> vertex.get("name")); + Map sourceByName = FpKit.groupingByUniqueKey(sourceVertices, Vertex::getName); + Map targetByName = FpKit.groupingByUniqueKey(targetVertices, Vertex::getName); for (Vertex sourceVertex : sourceVertices) { - Vertex targetVertex = targetByName.get((String) sourceVertex.get("name")); + Vertex targetVertex = targetByName.get(sourceVertex.getName()); if (targetVertex == null) { deleted.add(sourceVertex); } else { @@ -668,86 +668,91 @@ private boolean isMappingPossible(Vertex v, FillupIsolatedVertices.IsolatedVertices isolatedInfo ) { - Vertex forcedMatch = forcedMatchingCache.get(v); - if (forcedMatch != null) { - return forcedMatch == u; - } - if (v.isIsolated() && u.isIsolated()) { - return false; - } - Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; - Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; - - if (v.isIsolated()) { - if (u.isBuiltInType()) { - return isolatedBuiltInSourceVertices.contains(v); - } else { - return isolatedInfo.mappingPossibleForIsolatedSource(v, u); -// if (u.getType().equals(FIELD)) { -// Vertex fieldsContainer = targetGraph.getFieldsContainerForField(u); -// String containerName = fieldsContainer.get("name"); -// String fieldName = u.get("name"); -// if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { -// return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); -// } -// } -// if (u.getType().equals(INPUT_FIELD)) { -// Vertex inputObject = targetGraph.getInputObjectForInputField(u); -// String inputObjectName = inputObject.get("name"); -// String fieldName = u.get("name"); -// if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { -// return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); -// } -// } -// return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); - } - } - if (u.isIsolated()) { - if (v.isBuiltInType()) { - return isolatedBuiltInTargetVertices.contains(u); - } else { - return isolatedInfo.mappingPossibleForIsolatedTarget(v, u); -// if (v.getType().equals(FIELD)) { -// Vertex fieldsContainer = sourceGraph.getFieldsContainerForField(v); -// String containerName = fieldsContainer.get("name"); -// String fieldName = v.get("name"); -// if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { -// return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); -// } -// } -// if (v.getType().equals(INPUT_FIELD)) { -// Vertex inputObject = sourceGraph.getInputObjectForInputField(v); -// String inputObjectName = inputObject.get("name"); -// String fieldName = v.get("name"); -// if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { -// return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); -// } -// } -// return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); - } - } - // the types of the vertices need to match: we don't allow to change the type - if (!v.getType().equals(u.getType())) { - return false; - } - Boolean result = checkNamedTypes(v, u, targetGraph); - if (result != null) { - return result; - } - result = checkNamedTypes(u, v, sourceGraph); - if (result != null) { - return result; - } - result = checkSpecificTypes(v, u, sourceGraph, targetGraph); - if (result != null) { - return result; - } - result = checkSpecificTypes(u, v, targetGraph, sourceGraph); - if (result != null) { - return result; - } +// Vertex forcedMatch = forcedMatchingCache.get(v); +// if (forcedMatch != null) { +// return forcedMatch == u; +// } +// if (v.isIsolated() && u.isIsolated()) { +// return false; +// } +// Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; +// Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; - return true; +// if (v.isIsolated()) { +// if (u.isBuiltInType()) { +// return isolatedBuiltInSourceVertices.contains(v); +// } +// } +// } else { +// return isolatedInfo.mappingPossible(v, u); +//// if (u.getType().equals(FIELD)) { +//// Vertex fieldsContainer = targetGraph.getFieldsContainerForField(u); +//// String containerName = fieldsContainer.get("name"); +//// String fieldName = u.get("name"); +//// if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { +//// return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); +//// } +//// } +//// if (u.getType().equals(INPUT_FIELD)) { +//// Vertex inputObject = targetGraph.getInputObjectForInputField(u); +//// String inputObjectName = inputObject.get("name"); +//// String fieldName = u.get("name"); +//// if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { +//// return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); +//// } +//// } +//// return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); +// } +// } +// if (u.isIsolated()) { +// if (v.isBuiltInType()) { +// return isolatedBuiltInTargetVertices.contains(u); +// } +// } +// } else { +// return isolatedInfo.mappingPossibleForIsolatedTarget(v, u); +//// if (v.getType().equals(FIELD)) { +//// Vertex fieldsContainer = sourceGraph.getFieldsContainerForField(v); +//// String containerName = fieldsContainer.get("name"); +//// String fieldName = v.get("name"); +//// if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { +//// return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); +//// } +//// } +//// if (v.getType().equals(INPUT_FIELD)) { +//// Vertex inputObject = sourceGraph.getInputObjectForInputField(v); +//// String inputObjectName = inputObject.get("name"); +//// String fieldName = v.get("name"); +//// if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { +//// return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); +//// } +//// } +//// return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); +// } +// } +// // the types of the vertices need to match: we don't allow to change the type +// if (!v.getType().equals(u.getType())) { +// return false; +// } +// Boolean result = checkNamedTypes(v, u, targetGraph); +// if (result != null) { +// return result; +// } +// result = checkNamedTypes(u, v, sourceGraph); +// if (result != null) { +// return result; +// } +// result = checkSpecificTypes(v, u, sourceGraph, targetGraph); +// if (result != null) { +// return result; +// } +// result = checkSpecificTypes(u, v, targetGraph, sourceGraph); +// if (result != null) { +// return result; +// } +// +// return true; + return isolatedInfo.mappingPossible(v, u); } private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph) { From a9b8d9a371a5745123a084cecb5024994053807d Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 14 Feb 2022 20:01:27 +1100 Subject: [PATCH 052/294] wip --- .../diffing/FillupIsolatedVertices.java | 136 +++++++++--------- .../graphql/schema/diffing/SchemaDiffing.java | 131 ++++++++--------- .../schema/diffing/SchemaDiffingTest.groovy | 1 - 3 files changed, 134 insertions(+), 134 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 7d841c6e04..5ec407c7c0 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -39,7 +39,7 @@ public class FillupIsolatedVertices { SchemaGraph targetGraph; IsolatedVertices isolatedVertices; - static Map> typeContexts = new LinkedHashMap<>(); + static Map> typeContexts = new LinkedHashMap<>(); static { typeContexts.put(FIELD, fieldContext()); @@ -58,8 +58,8 @@ public class FillupIsolatedVertices { typeContexts.put(DIRECTIVE, directiveContext()); } - private static List inputFieldContexts() { - IsolatedVertexContext inputFieldType = new IsolatedVertexContext() { + private static List inputFieldContexts() { + VertexContextSegment inputFieldType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -70,7 +70,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return INPUT_FIELD.equals(vertex.getType()); } }; - IsolatedVertexContext inputObjectContext = new IsolatedVertexContext() { + VertexContextSegment inputObjectContext = new VertexContextSegment() { @Override public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) { Vertex inputObject = schemaGraph.getInputObjectForInputField(inputField); @@ -82,7 +82,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext inputFieldName = new IsolatedVertexContext() { + VertexContextSegment inputFieldName = new VertexContextSegment() { @Override public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) { return inputField.getName(); @@ -93,13 +93,13 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(inputFieldType, inputObjectContext, inputFieldName); + List contexts = Arrays.asList(inputFieldType, inputObjectContext, inputFieldName); return contexts; } - private static List dummyTypeContext() { - IsolatedVertexContext dummyType = new IsolatedVertexContext() { + private static List dummyTypeContext() { + VertexContextSegment dummyType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -110,12 +110,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return DUMMY_TYPE_VERTEX.equals(vertex.getType()); } }; - List contexts = Arrays.asList(dummyType); + List contexts = Arrays.asList(dummyType); return contexts; } - private static List scalarContext() { - IsolatedVertexContext scalar = new IsolatedVertexContext() { + private static List scalarContext() { + VertexContextSegment scalar = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -126,12 +126,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return SCALAR.equals(vertex.getType()); } }; - List contexts = Arrays.asList(scalar); + List contexts = Arrays.asList(scalar); return contexts; } - private static List inputObjectContext() { - IsolatedVertexContext inputObject = new IsolatedVertexContext() { + private static List inputObjectContext() { + VertexContextSegment inputObject = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -142,12 +142,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return INPUT_OBJECT.equals(vertex.getType()); } }; - List contexts = Arrays.asList(inputObject); + List contexts = Arrays.asList(inputObject); return contexts; } - private static List objectContext() { - IsolatedVertexContext objectType = new IsolatedVertexContext() { + private static List objectContext() { + VertexContextSegment objectType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -159,7 +159,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - IsolatedVertexContext objectName = new IsolatedVertexContext() { + VertexContextSegment objectName = new VertexContextSegment() { @Override public String idForVertex(Vertex object, SchemaGraph schemaGraph) { return object.getName(); @@ -170,12 +170,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(objectType, objectName); + List contexts = Arrays.asList(objectType, objectName); return contexts; } - private static List enumContext() { - IsolatedVertexContext enumCtxType = new IsolatedVertexContext() { + private static List enumContext() { + VertexContextSegment enumCtxType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -186,7 +186,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return ENUM.equals(vertex.getType()); } }; - IsolatedVertexContext enumName = new IsolatedVertexContext() { + VertexContextSegment enumName = new VertexContextSegment() { @Override public String idForVertex(Vertex enumVertex, SchemaGraph schemaGraph) { return enumVertex.getName(); @@ -197,12 +197,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(enumCtxType, enumName); + List contexts = Arrays.asList(enumCtxType, enumName); return contexts; } - private static List enumValueContext() { - IsolatedVertexContext enumValueType = new IsolatedVertexContext() { + private static List enumValueContext() { + VertexContextSegment enumValueType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -213,7 +213,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return ENUM_VALUE.equals(vertex.getType()); } }; - IsolatedVertexContext enumValueName = new IsolatedVertexContext() { + VertexContextSegment enumValueName = new VertexContextSegment() { @Override public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) { return enumValue.getName(); @@ -224,12 +224,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(enumValueType, enumValueName); + List contexts = Arrays.asList(enumValueType, enumValueName); return contexts; } - private static List interfaceContext() { - IsolatedVertexContext interfaceType = new IsolatedVertexContext() { + private static List interfaceContext() { + VertexContextSegment interfaceType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -240,7 +240,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return INTERFACE.equals(vertex.getType()); } }; - IsolatedVertexContext interfaceName = new IsolatedVertexContext() { + VertexContextSegment interfaceName = new VertexContextSegment() { @Override public String idForVertex(Vertex interfaceVertex, SchemaGraph schemaGraph) { return interfaceVertex.getName(); @@ -252,12 +252,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(interfaceType, interfaceName); + List contexts = Arrays.asList(interfaceType, interfaceName); return contexts; } - private static List unionContext() { - IsolatedVertexContext unionType = new IsolatedVertexContext() { + private static List unionContext() { + VertexContextSegment unionType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -268,7 +268,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return UNION.equals(vertex.getType()); } }; - IsolatedVertexContext unionName = new IsolatedVertexContext() { + VertexContextSegment unionName = new VertexContextSegment() { @Override public String idForVertex(Vertex union, SchemaGraph schemaGraph) { return union.getName(); @@ -280,12 +280,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(unionType, unionName); + List contexts = Arrays.asList(unionType, unionName); return contexts; } - private static List directiveContext() { - IsolatedVertexContext directiveType = new IsolatedVertexContext() { + private static List directiveContext() { + VertexContextSegment directiveType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -296,7 +296,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return DIRECTIVE.equals(vertex.getType()); } }; - IsolatedVertexContext directiveName = new IsolatedVertexContext() { + VertexContextSegment directiveName = new VertexContextSegment() { @Override public String idForVertex(Vertex directive, SchemaGraph schemaGraph) { return directive.getName(); @@ -308,12 +308,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(directiveType); + List contexts = Arrays.asList(directiveType); return contexts; } - private static List appliedDirectiveContext() { - IsolatedVertexContext appliedDirectiveType = new IsolatedVertexContext() { + private static List appliedDirectiveContext() { + VertexContextSegment appliedDirectiveType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -324,7 +324,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return APPLIED_DIRECTIVE.equals(vertex.getType()); } }; - IsolatedVertexContext appliedDirectiveName = new IsolatedVertexContext() { + VertexContextSegment appliedDirectiveName = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { return appliedDirective.getName(); @@ -336,12 +336,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(appliedDirectiveType, appliedDirectiveName); + List contexts = Arrays.asList(appliedDirectiveType, appliedDirectiveName); return contexts; } - private static List appliedArgumentContext() { - IsolatedVertexContext appliedArgumentType = new IsolatedVertexContext() { + private static List appliedArgumentContext() { + VertexContextSegment appliedArgumentType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -352,7 +352,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return APPLIED_ARGUMENT.equals(vertex.getType()); } }; - IsolatedVertexContext appliedDirective = new IsolatedVertexContext() { + VertexContextSegment appliedDirective = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); @@ -364,7 +364,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext appliedDirectiveContainer = new IsolatedVertexContext() { + VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); @@ -377,7 +377,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext parentOfAppliedDirectiveContainer = new IsolatedVertexContext() { + VertexContextSegment parentOfAppliedDirectiveContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); @@ -391,7 +391,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext appliedArgumentName = new IsolatedVertexContext() { + VertexContextSegment appliedArgumentName = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { return appliedArgument.getName(); @@ -402,12 +402,12 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(appliedArgumentType, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective, appliedArgumentName); + List contexts = Arrays.asList(appliedArgumentType, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective, appliedArgumentName); return contexts; } - private static List fieldContext() { - IsolatedVertexContext field = new IsolatedVertexContext() { + private static List fieldContext() { + VertexContextSegment field = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -418,7 +418,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return FIELD.equals(vertex.getType()); } }; - IsolatedVertexContext container = new IsolatedVertexContext() { + VertexContextSegment container = new VertexContextSegment() { @Override public String idForVertex(Vertex field, SchemaGraph schemaGraph) { Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(field); @@ -431,7 +431,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - IsolatedVertexContext fieldName = new IsolatedVertexContext() { + VertexContextSegment fieldName = new VertexContextSegment() { @Override public String idForVertex(Vertex field, SchemaGraph schemaGraph) { return field.getName(); @@ -442,13 +442,13 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(field, container, fieldName); + List contexts = Arrays.asList(field, container, fieldName); return contexts; } - private static List argumentsForFieldsContexts() { + private static List argumentsForFieldsContexts() { - IsolatedVertexContext argumentType = new IsolatedVertexContext() { + VertexContextSegment argumentType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { return vertex.getType(); @@ -460,7 +460,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - IsolatedVertexContext fieldOrDirective = new IsolatedVertexContext() { + VertexContextSegment fieldOrDirective = new VertexContextSegment() { @Override public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); @@ -472,7 +472,7 @@ public boolean filter(Vertex argument, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext containerOrDirectiveHolder = new IsolatedVertexContext() { + VertexContextSegment containerOrDirectiveHolder = new VertexContextSegment() { @Override public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument); @@ -491,7 +491,7 @@ public boolean filter(Vertex argument, SchemaGraph schemaGraph) { return true; } }; - IsolatedVertexContext argumentName = new IsolatedVertexContext() { + VertexContextSegment argumentName = new VertexContextSegment() { @Override public String idForVertex(Vertex argument, SchemaGraph schemaGraph) { return argument.getName(); @@ -502,7 +502,7 @@ public boolean filter(Vertex argument, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(argumentType, containerOrDirectiveHolder, fieldOrDirective, argumentName); + List contexts = Arrays.asList(argumentType, containerOrDirectiveHolder, fieldOrDirective, argumentName); return contexts; } @@ -544,7 +544,7 @@ public void ensureGraphAreSameSize() { } - public abstract static class IsolatedVertexContext { + public abstract static class VertexContextSegment { public abstract String idForVertex(Vertex vertex, SchemaGraph schemaGraph); @@ -634,7 +634,7 @@ public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { } - public void calcIsolatedVertices(List contexts, String typeNameForDebug) { + public void calcIsolatedVertices(List contexts, String typeNameForDebug) { Collection currentSourceVertices = sourceGraph.getVertices(); Collection currentTargetVertices = targetGraph.getVertices(); calcIsolatedVerticesImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); @@ -648,7 +648,7 @@ private void calcIsolatedVerticesImpl( Collection currentTargetVertices, List currentContextId, int curContextSegmentIx, - List contextSegments, + List contextSegments, Set usedSourceVertices, Set usedTargetVertices, String typeNameForDebug) { @@ -657,7 +657,7 @@ private void calcIsolatedVerticesImpl( * the elements grouped by the current context segment. */ - IsolatedVertexContext finalCurrentContext = contextSegments.get(curContextSegmentIx); + VertexContextSegment finalCurrentContext = contextSegments.get(curContextSegmentIx); Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, v -> finalCurrentContext.filter(v, sourceGraph), v -> finalCurrentContext.idForVertex(v, sourceGraph)); @@ -740,7 +740,7 @@ private void calcIsolatedVerticesImpl( } - public void calcPossibleMappings(List contexts, String typeNameForDebug) { + public void calcPossibleMappings(List contexts, String typeNameForDebug) { Collection currentSourceVertices = sourceGraph.getVertices(); Collection currentTargetVertices = targetGraph.getVertices(); calcPossibleMappingImpl(currentSourceVertices, @@ -761,12 +761,12 @@ private void calcPossibleMappingImpl( Collection currentTargetVertices, List contextId, int contextIx, - List contexts, + List contexts, Set usedSourceVertices, Set usedTargetVertices, String typeNameForDebug) { - IsolatedVertexContext finalCurrentContext = contexts.get(contextIx); + VertexContextSegment finalCurrentContext = contexts.get(contextIx); Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, v -> finalCurrentContext.filter(v, sourceGraph), v -> finalCurrentContext.idForVertex(v, sourceGraph)); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index a853f48e77..76fb3e67d9 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -13,7 +13,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -27,7 +27,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -170,7 +169,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t System.out.println("graph size: " + graphSize); if (sizeDiff != 0) { - sortSourceGraph(sourceGraph, targetGraph); + sortSourceGraph(sourceGraph, targetGraph, isolatedVertices); } AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); @@ -180,7 +179,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); if (compareResult == 0) { - return (-1) * Integer.compare(mappingEntry1.level, mappingEntry2.level); + return Integer.compare(mappingEntry2.level, mappingEntry1.level); } else { return compareResult; } @@ -233,67 +232,70 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t } - private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph) { + private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { + // we sort descending by number of possible target vertices + Collections.sort(sourceGraph.getVertices(), (o1, o2) -> Integer.compare(isolatedVertices.possibleMappings.get(o2).size(), isolatedVertices.possibleMappings.get(o1).size())); - - // how often does each source edge (based on the label) appear in target graph - Map targetLabelCount = new LinkedHashMap<>(); - for (Edge targetEdge : targetGraph.getEdges()) { - targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); - } - // how often does each source vertex (based on the data) appear in the target graph - Map targetVertexDataCount = new LinkedHashMap<>(); - for (Vertex targetVertex : targetGraph.getVertices()) { - targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); - } - - // an infrequency weight is 1 - count in target. Meaning the higher the - // value, the smaller the count, the less frequent it. - // Higher Infrequency => more unique is the vertex/label - Map vertexInfrequencyWeights = new LinkedHashMap<>(); - Map edgesInfrequencyWeights = new LinkedHashMap<>(); - for (Vertex vertex : sourceGraph.getVertices()) { - vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); - } - for (Edge edge : sourceGraph.getEdges()) { - edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); - } - - /** - * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness - * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. - */ - - //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted - ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); - nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); - - Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); - nextCandidates.remove(nextCandidates.size() - 1); - - List result = new ArrayList<>(); - result.add(curVertex); - while (nextCandidates.size() > 0) { - Vertex nextOne = null; - int curMaxWeight = Integer.MIN_VALUE; - int index = 0; - int nextOneIndex = -1; - - // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices - for (Vertex candidate : nextCandidates) { - List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); - int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); - if (totalWeight > curMaxWeight) { - nextOne = candidate; - nextOneIndex = index; - curMaxWeight = totalWeight; - } - index++; - } - result.add(nextOne); - nextCandidates.remove(nextOneIndex); - } - sourceGraph.setVertices(result); +// +// +// // how often does each source edge (based on the label) appear in target graph +// Map targetLabelCount = new LinkedHashMap<>(); +// for (Edge targetEdge : targetGraph.getEdges()) { +// targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); +// } +// // how often does each source vertex (based on the data) appear in the target graph +// Map targetVertexDataCount = new LinkedHashMap<>(); +// for (Vertex targetVertex : targetGraph.getVertices()) { +// targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); +// } +// +// // an infrequency weight is 1 - count in target. Meaning the higher the +// // value, the smaller the count, the less frequent it. +// // Higher Infrequency => more unique is the vertex/label +// Map vertexInfrequencyWeights = new LinkedHashMap<>(); +// Map edgesInfrequencyWeights = new LinkedHashMap<>(); +// for (Vertex vertex : sourceGraph.getVertices()) { +// vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); +// } +// for (Edge edge : sourceGraph.getEdges()) { +// edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); +// } +// +// /** +// * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness +// * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. +// */ +// +// //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted +// ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); +// nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); +// +// Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); +// nextCandidates.remove(nextCandidates.size() - 1); +// +// List result = new ArrayList<>(); +// result.add(curVertex); +// while (nextCandidates.size() > 0) { +// Vertex nextOne = null; +// int curMaxWeight = Integer.MIN_VALUE; +// int index = 0; +// int nextOneIndex = -1; +// +// // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices +// for (Vertex candidate : nextCandidates) { +// List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); +// int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); +// if (totalWeight > curMaxWeight) { +// nextOne = candidate; +// nextOneIndex = index; +// curMaxWeight = totalWeight; +// } +// index++; +// } +// result.add(nextOne); +// nextCandidates.remove(nextOneIndex); +// } +// sourceGraph.setVertices(result); } private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { @@ -1026,6 +1028,5 @@ private double calcLowerBoundMappingCost(Vertex v, return result; } - AtomicInteger zeroResult = new AtomicInteger(); } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 903197a369..4d6ffa67c4 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -363,7 +363,6 @@ class SchemaDiffingTest extends Specification { } - @Ignore def "change large schema a bit"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) From 36946efcd7af7e3bf8a9e5fbfe0e9c047d5b35ff Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 14 Feb 2022 20:07:59 +1100 Subject: [PATCH 053/294] wip --- .../schema/diffing/FillupIsolatedVertices.java | 15 +++++++++++++-- .../java/graphql/schema/diffing/SchemaGraph.java | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 5ec407c7c0..1818f30614 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -213,6 +213,17 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return ENUM_VALUE.equals(vertex.getType()); } }; + VertexContextSegment enumName = new VertexContextSegment() { + @Override + public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) { + return schemaGraph.getEnumForEnumValue(enumValue).getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; VertexContextSegment enumValueName = new VertexContextSegment() { @Override public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) { @@ -224,7 +235,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(enumValueType, enumValueName); + List contexts = Arrays.asList(enumValueType, enumName, enumValueName); return contexts; } @@ -534,8 +545,8 @@ public void ensureGraphAreSameSize() { sourceGraph.addVertices(isolatedVertices.allIsolatedSource); targetGraph.addVertices(isolatedVertices.allIsolatedTarget); - System.out.println("done isolated"); Assert.assertTrue(sourceGraph.size() == targetGraph.size()); + System.out.println("done isolated"); // if (sourceGraph.size() < targetGraph.size()) { // isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); // } else if (sourceGraph.size() > targetGraph.size()) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 1829836694..acd7ffe303 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -20,7 +20,6 @@ import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; import static java.lang.String.format; -import static java.lang.String.valueOf; public class SchemaGraph { @@ -249,4 +248,9 @@ public Vertex getParentSchemaElement(Vertex vertex) { return assertShouldNeverHappen(); } + public Vertex getEnumForEnumValue(Vertex enumValue) { + List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); + assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for %s", enumValue)); + return adjacentVertices.get(0); + } } From f39a46a02515b317f453e5c0fca77a5b21195de7 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 14 Feb 2022 20:12:38 +1100 Subject: [PATCH 054/294] wip --- .../diffing/FillupIsolatedVertices.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 1818f30614..a5221f08be 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -126,7 +126,18 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return SCALAR.equals(vertex.getType()); } }; - List contexts = Arrays.asList(scalar); + VertexContextSegment scalarName = new VertexContextSegment() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(scalar, scalarName); return contexts; } @@ -319,7 +330,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(directiveType); + List contexts = Arrays.asList(directiveType, directiveName); return contexts; } @@ -546,6 +557,12 @@ public void ensureGraphAreSameSize() { targetGraph.addVertices(isolatedVertices.allIsolatedTarget); Assert.assertTrue(sourceGraph.size() == targetGraph.size()); + for (Vertex vertex : isolatedVertices.possibleMappings.keySet()) { + Collection vertices = isolatedVertices.possibleMappings.get(vertex); + if (vertices.size() > 1) { + System.out.println("multiple for " + vertex); + } + } System.out.println("done isolated"); // if (sourceGraph.size() < targetGraph.size()) { // isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); From d134676b5541899bb906ee204d8b0875e1cfce44 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 14 Feb 2022 23:38:21 +1100 Subject: [PATCH 055/294] wip --- .../diffing/FillupIsolatedVertices.java | 51 +++++++++++++++++-- .../graphql/schema/diffing/SchemaDiffing.java | 17 +++++-- .../graphql/schema/diffing/SchemaGraph.java | 6 +++ .../schema/diffing/SchemaDiffingTest.groovy | 7 ++- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index a5221f08be..a076f2de93 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -32,6 +32,8 @@ import static graphql.schema.diffing.SchemaGraph.SCALAR; import static graphql.schema.diffing.SchemaGraph.UNION; import static graphql.util.FpKit.concat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; public class FillupIsolatedVertices { @@ -99,6 +101,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { private static List dummyTypeContext() { + VertexContextSegment dummyType = new VertexContextSegment() { @Override public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { @@ -110,7 +113,36 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return DUMMY_TYPE_VERTEX.equals(vertex.getType()); } }; - List contexts = Arrays.asList(dummyType); + VertexContextSegment inputObjectOrFieldContainerContext = new VertexContextSegment() { + @Override + public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { + Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); + if (fieldOrInputField.getType().equals(FIELD)) { + return schemaGraph.getFieldsContainerForField(fieldOrInputField).getName(); + } else { + return schemaGraph.getInputObjectForInputField(fieldOrInputField).getName(); + } + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + VertexContextSegment inputFieldOrFieldName = new VertexContextSegment() { + @Override + public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { + Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); + return fieldOrInputField.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + List contexts = Arrays.asList(dummyType, inputObjectOrFieldContainerContext, inputFieldOrFieldName); return contexts; } @@ -536,7 +568,6 @@ public FillupIsolatedVertices(SchemaGraph sourceGraph, SchemaGraph targetGraph) } public void ensureGraphAreSameSize() { -// calcIsolatedVertices(typeContexts.get(FIELD), FIELD); calcPossibleMappings(typeContexts.get(FIELD), FIELD); calcPossibleMappings(typeContexts.get(ARGUMENT), ARGUMENT); calcPossibleMappings(typeContexts.get(INPUT_FIELD), INPUT_FIELD); @@ -574,6 +605,20 @@ public void ensureGraphAreSameSize() { public abstract static class VertexContextSegment { + private List children; + + public VertexContextSegment(List children) { + this.children = children; + } + + public VertexContextSegment() { + this.children = emptyList(); + } + + public VertexContextSegment(VertexContextSegment child) { + this.children = singletonList(child); + } + public abstract String idForVertex(Vertex vertex, SchemaGraph schemaGraph); public abstract boolean filter(Vertex vertex, SchemaGraph schemaGraph); @@ -662,7 +707,7 @@ public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { } - public void calcIsolatedVertices(List contexts, String typeNameForDebug) { + private void calcIsolatedVertices(List contexts, String typeNameForDebug) { Collection currentSourceVertices = sourceGraph.getVertices(); Collection currentTargetVertices = targetGraph.getVertices(); calcIsolatedVerticesImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 76fb3e67d9..c461239a16 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -188,7 +188,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); -// System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); // if ((++counter) % 100 == 0) { // System.out.println((counter) + " entry at level"); // } @@ -234,7 +234,18 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { // we sort descending by number of possible target vertices - Collections.sort(sourceGraph.getVertices(), (o1, o2) -> Integer.compare(isolatedVertices.possibleMappings.get(o2).size(), isolatedVertices.possibleMappings.get(o1).size())); + Collections.sort(sourceGraph.getVertices(), (v1, v2) -> + + { + + int v2Count = v2.isBuiltInType() ? -1 : (v2.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v2).size()); + int v1Count = v1.isBuiltInType() ? -1 : (v1.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v1).size()); + return Integer.compare(v2Count, v1Count); + }); + + for (Vertex vertex : sourceGraph.getVertices()) { + System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); + } // // @@ -669,6 +680,7 @@ private boolean isMappingPossible(Vertex v, Set partialMappingTargetSet, FillupIsolatedVertices.IsolatedVertices isolatedInfo ) { + return isolatedInfo.mappingPossible(v, u); // Vertex forcedMatch = forcedMatchingCache.get(v); // if (forcedMatch != null) { @@ -754,7 +766,6 @@ private boolean isMappingPossible(Vertex v, // } // // return true; - return isolatedInfo.mappingPossible(v, u); } private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph) { diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index acd7ffe303..7d27ba7ad8 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -253,4 +253,10 @@ public Vertex getEnumForEnumValue(Vertex enumValue) { assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for %s", enumValue)); return adjacentVertices.get(0); } + + public Vertex getFieldOrInputFieldForDummyType(Vertex enumValue) { + List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(INPUT_FIELD)); + assertTrue(adjacentVertices.size() == 1, () -> format("No field or input field found for %s", enumValue)); + return adjacentVertices.get(0); + } } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 4d6ffa67c4..ddd8c4cbeb 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -7,7 +7,6 @@ import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer import graphql.util.TraversalControl import graphql.util.TraverserContext -import spock.lang.Ignore import spock.lang.Specification import static graphql.TestUtil.schema @@ -384,7 +383,6 @@ class SchemaDiffingTest extends Specification { diff.size() == 171 } - @Ignore def "change large schema a bit 2"() { given: def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) @@ -402,8 +400,9 @@ class SchemaDiffingTest extends Specification { println "deleted fields: " + counter when: def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) + diff.each { println it } then: - diff.size() == 171 + diff.size() == 855 } def "change object type name used twice"() { @@ -514,7 +513,7 @@ class SchemaDiffingTest extends Specification { } - def "changing schema a lot"() { + def "added different types and fields"() { given: def schema1 = schema(""" type Query { From 794688417028dcbc0c8c59f65ce99506c96771e9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 15 Feb 2022 07:09:35 +1100 Subject: [PATCH 056/294] wip --- .../groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index ddd8c4cbeb..8eae4b2888 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -402,7 +402,8 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) diff.each { println it } then: - diff.size() == 855 + // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 + diff.size() == 5 * 171 } def "change object type name used twice"() { From 3795fa2ebf8714b24839819f4ff2c261c458aaff Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 15 Feb 2022 08:14:45 +1100 Subject: [PATCH 057/294] wip --- src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 8eae4b2888..92d1dc220e 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -949,7 +949,7 @@ class SchemaDiffingTest extends Specification { * It would be less operations with f2 renamed to g3, but this would defy expectations. * */ - operations.size() == 7 + operations.size() == 11 } def "arguments in fields"() { From 2f17e6bdfa08a398470ccb6bc82d22fb550e739f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 15 Feb 2022 08:51:36 +1100 Subject: [PATCH 058/294] all tests green --- .../java/graphql/schema/diffing/FillupIsolatedVertices.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index a076f2de93..af92da555c 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -118,7 +118,8 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); if (fieldOrInputField.getType().equals(FIELD)) { - return schemaGraph.getFieldsContainerForField(fieldOrInputField).getName(); + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrInputField); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); } else { return schemaGraph.getInputObjectForInputField(fieldOrInputField).getName(); } From b42aab687001a0483f2df95823d4b7b47c2b800e Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Tue, 19 Jul 2022 13:12:24 -0500 Subject: [PATCH 059/294] Added workflow and gcp JS client library script with package json --- .../gql-perf-arch-github_workflow.yml | 70 +++++++++++++++++++ google_cloud_http_target_task.js | 63 +++++++++++++++++ package.json | 23 ++++++ 3 files changed, 156 insertions(+) create mode 100644 .github/workflows/gql-perf-arch-github_workflow.yml create mode 100644 google_cloud_http_target_task.js create mode 100644 package.json diff --git a/.github/workflows/gql-perf-arch-github_workflow.yml b/.github/workflows/gql-perf-arch-github_workflow.yml new file mode 100644 index 0000000000..e01cfe0947 --- /dev/null +++ b/.github/workflows/gql-perf-arch-github_workflow.yml @@ -0,0 +1,70 @@ +name: gql-perf-arch-github_workflow +on: + push: + branches: + - master + workflow_dispatch: + inputs: + COMMIT_HASH_INPUT: + description: 'Commit hash' + required: true + CLASSES_TO_EXECUTE_INPUT: + description: 'Classes to test' + required: false + PULL_REQUEST_NUMBER_INPUT: + description: 'Pull request number' + required: false +env: + #Actions secrets (keys) + PROJECT_ID: ${{ secrets.PROJECT_ID }} + QUEUE_ID: ${{ secrets.QUEUE_ID }} + LOCATION: ${{ secrets.LOCATION }} + WORKFLOW_URL: ${{ secrets.WORKFLOW_URL }} + SERVICE_ACCOUNT_EMAIL: ${{ secrets.SERVICE_ACCOUNT_EMAIL }} + #Payload variables + COMMIT_HASH: ${{ (github.sha) }} + CLASSES: ${{ (github.event.inputs.CLASSES_TO_EXECUTE_INPUT) }} + PULL_REQUEST_NUMBER: ${{ (github.event.inputs.PULL_REQUEST_NUMBER_INPUT) }} +jobs: + push_action: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '14' + - run: npm install + - run: npm i yenv + - run: npm i uuid + + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: google-github-actions/auth@v0.4.0 + with: + credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + + - name: Execute JS script + run: node google_cloud_http_target_task.js + + workflow_dispatch_action: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '14' + - run: npm install + - run: npm i yenv + - run: npm install uuid + - run: echo "COMMIT_HASH=${{ (github.event.inputs.COMMIT_HASH_INPUT) }} " >> $GITHUB_ENV + + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: google-github-actions/auth@v0.4.0 + with: + credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + + - name: Execute JS script + run: node google_cloud_http_target_task.js diff --git a/google_cloud_http_target_task.js b/google_cloud_http_target_task.js new file mode 100644 index 0000000000..878f08ac4f --- /dev/null +++ b/google_cloud_http_target_task.js @@ -0,0 +1,63 @@ +const {CloudTasksClient} = require('@google-cloud/tasks'); +const yenv = require('yenv') +const { v4: uuidv4 } = require('uuid'); +const fs = require('fs') +const os = require('os'); + +// Parse 'gql-perf-arch-github_workflow.yml' file +const parsedDocument = yenv('.github/workflows/gql-perf-arch-github_workflow.yml') + +// Instantiate a client +const client = new CloudTasksClient(); + +// Payload variables +const jobId = String(uuidv4()); +const commitHash = parsedDocument.COMMIT_HASH; +const classes = parsedDocument.CLASSES; +const pullRequestNumber = parsedDocument.PULL_REQUEST_NUMBER; + + +async function createHttpTaskWithToken() { + // Actions secrets (keys) + const project = parsedDocument.PROJECT_ID; + const queue = parsedDocument.QUEUE_ID; + const location = parsedDocument.LOCATION; + const url = parsedDocument.WORKFLOW_URL; + const serviceAccountEmail = parsedDocument.SERVICE_ACCOUNT_EMAIL; + + // Construct payload + const payloadStructure = { + "jobId": jobId, + "commitHash": commitHash, + "classes": classes, + "pullRequest": pullRequestNumber, + } + //Format payload + const parsedPayload = JSON.stringify(JSON.stringify(payloadStructure)); + const payload = `{"argument": ${parsedPayload}}`; + + console.log(`Payload: ${payload}`); + + // Construct the fully qualified queue name + const parent = client.queuePath(project, location, queue); + + // Construct task with oauth authorization + const task = { + httpRequest: { + httpMethod: 'POST', + url, + oauthToken: { + serviceAccountEmail, + }, + body: Buffer.from(payload).toString('base64'), + }, + }; + + console.log(`Task: ${task}`); + const request = {parent: parent, task: task}; + const [response] = await client.createTask(request); + const name = response.name; + console.log(`Created task ${name}`); + console.log(`Your job id is: ${jobId}`); +} +createHttpTaskWithToken(); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..d5fec09971 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "appengine-cloudtasks", + "description": "Google App Engine Cloud Tasks example.", + "license": "Apache-2.0", + "author": "Google Inc.", + "private": true, + "engines": { + "node": ">=12.0.0" + }, + "files": [ + "*.js" + ], + "dependencies": { + "@google-cloud/tasks": "^3.0.0", + "body-parser": "^1.18.3", + "express": "^4.16.3" + }, + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.0.0", + "uuid": "^8.0.0" + } +} From a5eb181c14e905bcb515b2e5868d99951a70c87b Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Wed, 20 Jul 2022 10:32:26 -0500 Subject: [PATCH 060/294] Refactored code --- .../google_cloud_http_target_task.js | 66 +++++++++++++++++++ .../gql-perf-arch-github_workflow.yml | 34 ++-------- google_cloud_http_target_task.js | 63 ------------------ package.json | 5 +- 4 files changed, 75 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/google_cloud_http_target_task.js delete mode 100644 google_cloud_http_target_task.js diff --git a/.github/workflows/google_cloud_http_target_task.js b/.github/workflows/google_cloud_http_target_task.js new file mode 100644 index 0000000000..fde292d77a --- /dev/null +++ b/.github/workflows/google_cloud_http_target_task.js @@ -0,0 +1,66 @@ +const { CloudTasksClient } = require('@google-cloud/tasks'); +const yenv = require('yenv') +const { v4: uuidv4 } = require('uuid'); + +const parsedDocument = yenv('.github/workflows/gql-perf-arch-github_workflow.yml') +const client = new CloudTasksClient(); + +const constructPayload = () => { + const jobId = String(uuidv4()); + const commitHash = parsedDocument.COMMIT_HASH; + const classes = parsedDocument.CLASSES; + const pullRequestNumber = parsedDocument.PULL_REQUEST_NUMBER; + + const payloadStructure = { + "jobId": jobId, + "commitHash": commitHash, + "classes": classes, + "pullRequest": pullRequestNumber, + } + return payloadStructure; +} + +const formatPayload = (payloadStructure) => { + const parsedPayload = JSON.stringify(JSON.stringify(payloadStructure)); + const payload = `{"argument": ${parsedPayload}}`; + console.log(`Payload: ${payload}`); + return payload; +} + +const constructTask = (serviceAccountEmail, payload, url) => { + const task = { + httpRequest: { + httpMethod: 'POST', + url, + oauthToken: { + serviceAccountEmail, + }, + body: Buffer.from(payload).toString('base64'), + }, + }; + return task; +} + +const createRequestBody = (payload) => { + const project = parsedDocument.PROJECT_ID; + const queue = parsedDocument.QUEUE_ID; + const location = parsedDocument.LOCATION; + const url = parsedDocument.WORKFLOW_URL; + const serviceAccountEmail = parsedDocument.SERVICE_ACCOUNT_EMAIL; + const requestBody = { + "fullyQualifiedQueueName": client.queuePath(project, location, queue), + "task": constructTask(serviceAccountEmail, payload, url) + } + return requestBody; +} + +async function createHttpTaskWithToken() { + const payloadStructure = constructPayload(); + const payload = formatPayload(payloadStructure); + const requestBody = createRequestBody(payload); + const request = { parent: requestBody.fullyQualifiedQueueName, task: requestBody.task }; + const [response] = await client.createTask(request); + const name = response.name; + console.log(`Created task ${name}`); +} +createHttpTaskWithToken(); diff --git a/.github/workflows/gql-perf-arch-github_workflow.yml b/.github/workflows/gql-perf-arch-github_workflow.yml index e01cfe0947..b2bd9ba135 100644 --- a/.github/workflows/gql-perf-arch-github_workflow.yml +++ b/.github/workflows/gql-perf-arch-github_workflow.yml @@ -2,7 +2,7 @@ name: gql-perf-arch-github_workflow on: push: branches: - - master + - main workflow_dispatch: inputs: COMMIT_HASH_INPUT: @@ -25,40 +25,20 @@ env: COMMIT_HASH: ${{ (github.sha) }} CLASSES: ${{ (github.event.inputs.CLASSES_TO_EXECUTE_INPUT) }} PULL_REQUEST_NUMBER: ${{ (github.event.inputs.PULL_REQUEST_NUMBER_INPUT) }} + jobs: - push_action: + execute_workflow: runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' }} steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '14' - run: npm install - - run: npm i yenv - - run: npm i uuid - - - id: 'auth' - name: 'Authenticate to Google Cloud' - uses: google-github-actions/auth@v0.4.0 - with: - credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - - name: Execute JS script - run: node google_cloud_http_target_task.js - - workflow_dispatch_action: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: '14' - - run: npm install - - run: npm i yenv - - run: npm install uuid - - run: echo "COMMIT_HASH=${{ (github.event.inputs.COMMIT_HASH_INPUT) }} " >> $GITHUB_ENV + - name: Update COMMIT_HASH + if: ${{ github.event_name == 'workflow_dispatch' }} + run: echo "COMMIT_HASH=${{ (github.event.inputs.COMMIT_HASH_INPUT) }} " >> $GITHUB_ENV - id: 'auth' name: 'Authenticate to Google Cloud' @@ -67,4 +47,4 @@ jobs: credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - name: Execute JS script - run: node google_cloud_http_target_task.js + run: node .github/workflows/google_cloud_http_target_task.js diff --git a/google_cloud_http_target_task.js b/google_cloud_http_target_task.js deleted file mode 100644 index 878f08ac4f..0000000000 --- a/google_cloud_http_target_task.js +++ /dev/null @@ -1,63 +0,0 @@ -const {CloudTasksClient} = require('@google-cloud/tasks'); -const yenv = require('yenv') -const { v4: uuidv4 } = require('uuid'); -const fs = require('fs') -const os = require('os'); - -// Parse 'gql-perf-arch-github_workflow.yml' file -const parsedDocument = yenv('.github/workflows/gql-perf-arch-github_workflow.yml') - -// Instantiate a client -const client = new CloudTasksClient(); - -// Payload variables -const jobId = String(uuidv4()); -const commitHash = parsedDocument.COMMIT_HASH; -const classes = parsedDocument.CLASSES; -const pullRequestNumber = parsedDocument.PULL_REQUEST_NUMBER; - - -async function createHttpTaskWithToken() { - // Actions secrets (keys) - const project = parsedDocument.PROJECT_ID; - const queue = parsedDocument.QUEUE_ID; - const location = parsedDocument.LOCATION; - const url = parsedDocument.WORKFLOW_URL; - const serviceAccountEmail = parsedDocument.SERVICE_ACCOUNT_EMAIL; - - // Construct payload - const payloadStructure = { - "jobId": jobId, - "commitHash": commitHash, - "classes": classes, - "pullRequest": pullRequestNumber, - } - //Format payload - const parsedPayload = JSON.stringify(JSON.stringify(payloadStructure)); - const payload = `{"argument": ${parsedPayload}}`; - - console.log(`Payload: ${payload}`); - - // Construct the fully qualified queue name - const parent = client.queuePath(project, location, queue); - - // Construct task with oauth authorization - const task = { - httpRequest: { - httpMethod: 'POST', - url, - oauthToken: { - serviceAccountEmail, - }, - body: Buffer.from(payload).toString('base64'), - }, - }; - - console.log(`Task: ${task}`); - const request = {parent: parent, task: task}; - const [response] = await client.createTask(request); - const name = response.name; - console.log(`Created task ${name}`); - console.log(`Your job id is: ${jobId}`); -} -createHttpTaskWithToken(); \ No newline at end of file diff --git a/package.json b/package.json index d5fec09971..59317967a1 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,10 @@ "dependencies": { "@google-cloud/tasks": "^3.0.0", "body-parser": "^1.18.3", - "express": "^4.16.3" + "express": "^4.16.3", + "yenv": "^3.0.1" }, "devDependencies": { - "chai": "^4.2.0", - "mocha": "^8.0.0", "uuid": "^8.0.0" } } From 6a302e9b822c0169960a8a3a9baf4a0cde8ec39f Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Wed, 20 Jul 2022 11:40:38 -0500 Subject: [PATCH 061/294] Fixed branch field in yml file and dependencies in package json --- .github/workflows/gql-perf-arch-github_workflow.yml | 2 +- package.json | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gql-perf-arch-github_workflow.yml b/.github/workflows/gql-perf-arch-github_workflow.yml index b2bd9ba135..d910f79686 100644 --- a/.github/workflows/gql-perf-arch-github_workflow.yml +++ b/.github/workflows/gql-perf-arch-github_workflow.yml @@ -2,7 +2,7 @@ name: gql-perf-arch-github_workflow on: push: branches: - - main + - master workflow_dispatch: inputs: COMMIT_HASH_INPUT: diff --git a/package.json b/package.json index 59317967a1..90987a264a 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,7 @@ "@google-cloud/tasks": "^3.0.0", "body-parser": "^1.18.3", "express": "^4.16.3", - "yenv": "^3.0.1" - }, - "devDependencies": { + "yenv": "^3.0.1", "uuid": "^8.0.0" } } From a43e57bd1d2768326bad1d2b7015a81543c5f4a9 Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Fri, 22 Jul 2022 10:45:48 -0500 Subject: [PATCH 062/294] Added createRequest function and switched over to process env instead onf yenv --- ...loud_http_target_task.js => create_job.js} | 25 +++++++++++-------- ...ub_workflow.yml => invoke_test_runner.yml} | 7 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) rename .github/workflows/{google_cloud_http_target_task.js => create_job.js} (75%) rename .github/workflows/{gql-perf-arch-github_workflow.yml => invoke_test_runner.yml} (91%) diff --git a/.github/workflows/google_cloud_http_target_task.js b/.github/workflows/create_job.js similarity index 75% rename from .github/workflows/google_cloud_http_target_task.js rename to .github/workflows/create_job.js index fde292d77a..5d9a7bdeae 100644 --- a/.github/workflows/google_cloud_http_target_task.js +++ b/.github/workflows/create_job.js @@ -1,15 +1,13 @@ const { CloudTasksClient } = require('@google-cloud/tasks'); -const yenv = require('yenv') const { v4: uuidv4 } = require('uuid'); -const parsedDocument = yenv('.github/workflows/gql-perf-arch-github_workflow.yml') const client = new CloudTasksClient(); const constructPayload = () => { const jobId = String(uuidv4()); - const commitHash = parsedDocument.COMMIT_HASH; - const classes = parsedDocument.CLASSES; - const pullRequestNumber = parsedDocument.PULL_REQUEST_NUMBER; + const commitHash = process.env.COMMIT_HASH; + const classes = process.env.CLASSES; + const pullRequestNumber = process.env.PULL_REQUEST_NUMBER; const payloadStructure = { "jobId": jobId, @@ -42,11 +40,11 @@ const constructTask = (serviceAccountEmail, payload, url) => { } const createRequestBody = (payload) => { - const project = parsedDocument.PROJECT_ID; - const queue = parsedDocument.QUEUE_ID; - const location = parsedDocument.LOCATION; - const url = parsedDocument.WORKFLOW_URL; - const serviceAccountEmail = parsedDocument.SERVICE_ACCOUNT_EMAIL; + const project = process.env.PROJECT_ID; + const queue = process.env.QUEUE_ID; + const location = process.env.LOCATION; + const url = process.env.WORKFLOW_URL + const serviceAccountEmail = process.env.SERVICE_ACCOUNT_EMAIL; const requestBody = { "fullyQualifiedQueueName": client.queuePath(project, location, queue), "task": constructTask(serviceAccountEmail, payload, url) @@ -54,11 +52,16 @@ const createRequestBody = (payload) => { return requestBody; } -async function createHttpTaskWithToken() { +const constructRequest = () => { const payloadStructure = constructPayload(); const payload = formatPayload(payloadStructure); const requestBody = createRequestBody(payload); const request = { parent: requestBody.fullyQualifiedQueueName, task: requestBody.task }; + return request; +} + +async function createHttpTaskWithToken() { + const request = constructRequest(); const [response] = await client.createTask(request); const name = response.name; console.log(`Created task ${name}`); diff --git a/.github/workflows/gql-perf-arch-github_workflow.yml b/.github/workflows/invoke_test_runner.yml similarity index 91% rename from .github/workflows/gql-perf-arch-github_workflow.yml rename to .github/workflows/invoke_test_runner.yml index d910f79686..d9e81e2cf2 100644 --- a/.github/workflows/gql-perf-arch-github_workflow.yml +++ b/.github/workflows/invoke_test_runner.yml @@ -1,4 +1,4 @@ -name: gql-perf-arch-github_workflow +name: invoke_test_runner on: push: branches: @@ -15,12 +15,11 @@ on: description: 'Pull request number' required: false env: - #Actions secrets (keys) PROJECT_ID: ${{ secrets.PROJECT_ID }} QUEUE_ID: ${{ secrets.QUEUE_ID }} LOCATION: ${{ secrets.LOCATION }} - WORKFLOW_URL: ${{ secrets.WORKFLOW_URL }} SERVICE_ACCOUNT_EMAIL: ${{ secrets.SERVICE_ACCOUNT_EMAIL }} + WORKFLOW_URL: ${{ secrets.WORKFLOW_URL }} #Payload variables COMMIT_HASH: ${{ (github.sha) }} CLASSES: ${{ (github.event.inputs.CLASSES_TO_EXECUTE_INPUT) }} @@ -47,4 +46,4 @@ jobs: credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - name: Execute JS script - run: node .github/workflows/google_cloud_http_target_task.js + run: node .github/workflows/create_job.js From 6030b3ab14bd317cacb5cf09063c7d97779b2f63 Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Thu, 14 Jul 2022 09:16:25 +1000 Subject: [PATCH 063/294] Implement pretty printer --- .../java/graphql/language/AstPrinter.java | 19 +- .../java/graphql/language/PrettyPrinter.java | 458 +++++++++++++++ .../java/graphql/parser/CommentParser.java | 247 ++++++++ .../parser/GraphqlAntlrToLanguage.java | 118 ++-- .../parser/NodeToRuleCapturingParser.java | 49 ++ .../graphql/language/PrettyPrinterTest.groovy | 529 ++++++++++++++++++ 6 files changed, 1364 insertions(+), 56 deletions(-) create mode 100644 src/main/java/graphql/language/PrettyPrinter.java create mode 100644 src/main/java/graphql/parser/CommentParser.java create mode 100644 src/main/java/graphql/parser/NodeToRuleCapturingParser.java create mode 100644 src/test/groovy/graphql/language/PrettyPrinterTest.groovy diff --git a/src/main/java/graphql/language/AstPrinter.java b/src/main/java/graphql/language/AstPrinter.java index a6322cdda4..f6bd7aacc9 100644 --- a/src/main/java/graphql/language/AstPrinter.java +++ b/src/main/java/graphql/language/AstPrinter.java @@ -25,7 +25,7 @@ public class AstPrinter { private final boolean compactMode; - private AstPrinter(boolean compactMode) { + protected AstPrinter(boolean compactMode) { this.compactMode = compactMode; printers.put(Argument.class, argument()); printers.put(ArrayValue.class, value()); @@ -463,11 +463,11 @@ private String node(Node node, Class startClass) { } @SuppressWarnings("unchecked") - private NodePrinter _findPrinter(Node node) { + protected NodePrinter _findPrinter(Node node) { return _findPrinter(node, null); } - private NodePrinter _findPrinter(Node node, Class startClass) { + protected NodePrinter _findPrinter(Node node, Class startClass) { if (node == null) { return (out, type) -> { }; @@ -689,7 +689,7 @@ public static void printAst(Writer writer, Node node) { /** * This will print the Ast node in graphql language format in a compact manner, with no new lines - * and comments stripped out of the text. + * and descriptions stripped out of the text. * * @param node the AST node to print * @@ -712,7 +712,16 @@ private static void printImpl(StringBuilder writer, Node node, boolean compactMo * * @param the type of node */ - private interface NodePrinter { + protected interface NodePrinter { void print(StringBuilder out, T node); } + + /** + * Allow subclasses to replace a printer for a specific {@link Node} + * @param nodeClass the class of the {@link Node} + * @param nodePrinter the custom {@link NodePrinter} + */ + protected void replacePrinter(Class nodeClass, NodePrinter nodePrinter) { + this.printers.put(nodeClass, nodePrinter); + } } diff --git a/src/main/java/graphql/language/PrettyPrinter.java b/src/main/java/graphql/language/PrettyPrinter.java new file mode 100644 index 0000000000..81ebe16fc9 --- /dev/null +++ b/src/main/java/graphql/language/PrettyPrinter.java @@ -0,0 +1,458 @@ +package graphql.language; + +import graphql.PublicApi; +import graphql.collect.ImmutableKit; +import graphql.parser.CommentParser; +import graphql.parser.NodeToRuleCapturingParser; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static graphql.Assert.assertTrue; + +/** + * A printer that acts as a code formatter. + * + * This printer will preserve pretty much all elements from the source text, even those that are not part of the AST + * (and are thus discarded by the {@link AstPrinter}), like comments. + * + * @see AstPrinter + */ +@PublicApi +public class PrettyPrinter extends AstPrinter { + private final CommentParser commentParser; + private final PrettyPrinterOptions options; + + public PrettyPrinter(NodeToRuleCapturingParser.ParserContext parserContext) { + this(parserContext, PrettyPrinterOptions.defaultOptions); + } + + public PrettyPrinter(NodeToRuleCapturingParser.ParserContext parserContext, PrettyPrinterOptions options) { + super(false); + this.commentParser = new CommentParser(parserContext); + this.options = options; + + this.replacePrinter(DirectiveDefinition.class, directiveDefinition()); + this.replacePrinter(Document.class, document()); + this.replacePrinter(EnumTypeDefinition.class, enumTypeDefinition("enum")); + this.replacePrinter(EnumTypeExtensionDefinition.class, enumTypeDefinition("extend enum")); + this.replacePrinter(EnumValueDefinition.class, enumValueDefinition()); + this.replacePrinter(FieldDefinition.class, fieldDefinition()); + this.replacePrinter(InputObjectTypeDefinition.class, inputObjectTypeDefinition("input")); + this.replacePrinter(InputObjectTypeExtensionDefinition.class, inputObjectTypeDefinition("extend input")); + this.replacePrinter(InputValueDefinition.class, inputValueDefinition()); + this.replacePrinter(InterfaceTypeDefinition.class, implementingTypeDefinition("interface")); + this.replacePrinter(InterfaceTypeExtensionDefinition.class, implementingTypeDefinition("extend interface")); + this.replacePrinter(ObjectTypeDefinition.class, implementingTypeDefinition("type")); + this.replacePrinter(ObjectTypeExtensionDefinition.class, implementingTypeDefinition("extend type")); + this.replacePrinter(ScalarTypeDefinition.class, scalarTypeDefinition("scalar")); + this.replacePrinter(ScalarTypeExtensionDefinition.class, scalarTypeDefinition("extend scalar")); + this.replacePrinter(UnionTypeDefinition.class, unionTypeDefinition("union")); + this.replacePrinter(UnionTypeExtensionDefinition.class, unionTypeDefinition("extend union")); + } + + public String print(Node node) { + StringBuilder builder = new StringBuilder(); + + NodePrinter nodePrinter = this._findPrinter(node); + nodePrinter.print(builder, node); + + return builder.toString(); + } + + public static String print(String schemaDefinition, PrettyPrinterOptions options) { + NodeToRuleCapturingParser parser = new NodeToRuleCapturingParser(); + Document document = parser.parseDocument(schemaDefinition); + + return new PrettyPrinter(parser.getParserContext(), options).print(document); + } + + private NodePrinter document() { + return (out, node) -> { + String firstLineComment = commentParser.getCommentOnFirstLineOfDocument(node) + .map(this::comment) + .map(append("\n")) + .orElse(""); + + out.append(firstLineComment); + out.append(join(node.getDefinitions(), "\n\n")).append("\n"); + + String endComments = comments(commentParser.getCommentsAfterAllDefinitions(node), "\n"); + + out.append(endComments); + }; + } + + private NodePrinter directiveDefinition() { + return (out, node) -> { + out.append(outset(node)); + String locations = join(node.getDirectiveLocations(), " | "); + String repeatable = node.isRepeatable() ? "repeatable " : ""; + out.append("directive @") + .append(node.getName()) + .append(block(node.getInputValueDefinitions(), node, "(", ")", "\n", ", ", "")) + .append(" ") + .append(repeatable) + .append("on ") + .append(locations); + }; + } + + private NodePrinter enumTypeDefinition(String nodeName) { + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + nodeName, + node.getName(), + directives(node.getDirectives()), + block(node.getEnumValueDefinitions(), node, "{", "}", "\n", null, null) + )); + }; + } + + private NodePrinter enumValueDefinition() { + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + node.getName(), + directives(node.getDirectives()) + )); + }; + } + + private NodePrinter fieldDefinition() { + return (out, node) -> { + out.append(outset(node)); + out.append(node.getName()) + .append(block(node.getInputValueDefinitions(), node, "(", ")", "\n", ", ", "")) + .append(": ") + .append(spaced( + type(node.getType()), + directives(node.getDirectives()) + )); + }; + } + + private String type(Type type) { + if (type instanceof NonNullType) { + NonNullType inner = (NonNullType) type; + return wrap("", type(inner.getType()), "!"); + } else if (type instanceof ListType) { + ListType inner = (ListType) type; + return wrap("[", type(inner.getType()), "]"); + } else { + TypeName inner = (TypeName) type; + return inner.getName(); + } + } + + private NodePrinter inputObjectTypeDefinition(String nodeName) { + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + nodeName, + node.getName(), + directives(node.getDirectives()), + block(node.getInputValueDefinitions(), node, "{", "}", "\n", null, null) + )); + }; + } + + private NodePrinter inputValueDefinition() { + String nameTypeSep = ": "; + String defaultValueEquals = "= "; + return (out, node) -> { + Value defaultValue = node.getDefaultValue(); + out.append(outset(node)); + out.append(spaced( + node.getName() + nameTypeSep + type(node.getType()), + wrap(defaultValueEquals, defaultValue, ""), + directives(node.getDirectives()) + )); + }; + } + + + private > NodePrinter implementingTypeDefinition(String nodeName) { + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + nodeName, + node.getName(), + wrap("implements ", block(node.getImplements(), node, "", "", " &\n", " & ", ""), ""), + directives(node.getDirectives()), + block(node.getFieldDefinitions(), node, "{", "}", "\n", null, null) + )); + }; + } + + private NodePrinter scalarTypeDefinition(String nodeName) { + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + nodeName, + node.getName(), + directives(node.getDirectives()))); + }; + } + + private NodePrinter unionTypeDefinition(String nodeName) { + String barSep = " | "; + String equals = "= "; + return (out, node) -> { + out.append(outset(node)); + out.append(spaced( + nodeName, + node.getName(), + directives(node.getDirectives()), + equals + join(node.getMemberTypes(), barSep) + )); + }; + } + + private String node(Node node, Class startClass) { + if (startClass != null) { + assertTrue(startClass.isInstance(node), () -> "The starting class must be in the inherit tree"); + } + StringBuilder builder = new StringBuilder(); + + String comments = comments(commentParser.getLeadingComments(node), "\n"); + builder.append(comments); + + NodePrinter printer = _findPrinter(node, startClass); + printer.print(builder, node); + + commentParser.getTrailingComment(node) + .map(this::comment) + .map(prepend(" ")) + .ifPresent(builder::append); + + return builder.toString(); + } + + private boolean isEmpty(List list) { + return list == null || list.isEmpty(); + } + + private boolean isEmpty(String s) { + return s == null || s.trim().length() == 0; + } + + private List nvl(List list) { + return list != null ? list : ImmutableKit.emptyList(); + } + + // Description and comments positioned before the node + private String outset(Node node) { + String description = description(node); + String commentsAfter = comments(commentParser.getCommentsAfterDescription(node), "\n"); + + return description + commentsAfter; + } + + private String description(Node node) { + Description description = ((AbstractDescribedNode) node).getDescription(); + if (description == null || description.getContent() == null) { + return ""; + } + String s; + boolean startNewLine = description.getContent().length() > 0 && description.getContent().charAt(0) == '\n'; + if (description.isMultiLine()) { + s = "\"\"\"" + (startNewLine ? "" : "\n") + description.getContent() + "\n\"\"\"\n"; + } else { + s = "\"" + description.getContent() + "\"\n"; + } + return s; + } + + private String comment(Comment comment) { + return comments(Collections.singletonList(comment)); + } + + private String comments(List comments) { + return comments(comments, ""); + } + + private String comments(List comments, String suffix) { + return comments(comments, "", suffix); + } + + private String comments(List comments, String prefix, String suffix) { + if (comments.isEmpty()) { + return ""; + } + + return comments.stream() + .map(Comment::getContent) + .map(content -> "#" + content) + .collect(Collectors.joining("\n", prefix, suffix)); + } + + private String directives(List directives) { + return join(nvl(directives), " "); + } + + private String join(List nodes, String delim) { + return join(nodes, delim, "", ""); + } + + private String join(List nodes, String delim, String prefix, String suffix) { + StringBuilder joined = new StringBuilder(); + + joined.append(prefix); + boolean first = true; + for (T node : nodes) { + if (first) { + first = false; + } else { + joined.append(delim); + } + joined.append(node(node)); + } + + joined.append(suffix); + return joined.toString(); + } + + private String node(Node node) { + return node(node, null); + } + + private String spaced(String... args) { + return join(" ", args); + } + + private Function prepend(String prefix) { + return text -> prefix + text; + } + + private Function append(String suffix) { + return text -> text + suffix; + } + + private String join(String delim, String... args) { + StringBuilder builder = new StringBuilder(); + + boolean first = true; + for (final String arg : args) { + if (isEmpty(arg)) { + continue; + } + if (first) { + first = false; + } else { + builder.append(delim); + } + builder.append(arg); + } + + return builder.toString(); + } + + private String block(List nodes, Node parentNode, String prefix, String suffix, String separatorMultiline, String separatorSingleLine, String whenEmpty) { + if (isEmpty(nodes)) { + return whenEmpty != null ? whenEmpty : prefix + suffix; + } + + boolean hasDescriptions = nodes.stream() + .filter(node -> node instanceof AbstractDescribedNode) + .map(node -> (AbstractDescribedNode) node) + .map(AbstractDescribedNode::getDescription) + .anyMatch(Objects::nonNull); + + boolean hasTrailingComments = nodes.stream() + .map(commentParser::getTrailingComment) + .anyMatch(Optional::isPresent); + + boolean hasLeadingComments = nodes.stream() + .mapToLong(node -> commentParser.getLeadingComments(node).size()) + .sum() > 0; + + boolean isMultiline = hasDescriptions || hasTrailingComments || hasLeadingComments || separatorSingleLine == null; + + String appliedSeparator = isMultiline ? separatorMultiline : separatorSingleLine; + + String blockStart = commentParser.getBeginningOfBlockComment(parentNode, prefix) + .map(this::comment) + .map(commentText -> String.format("%s %s\n", prefix, commentText)) + .orElse(String.format("%s%s", prefix, (isMultiline ? "\n" : ""))); + + String blockEndComments = comments(commentParser.getEndOfBlockComments(parentNode, suffix), "\n", ""); + String blockEnd = (isMultiline ? "\n" : "") + suffix; + + String content = nodes.stream().map(this::node).collect(Collectors.joining(appliedSeparator)); + String possiblyIndentedContent = isMultiline ? indent(content + blockEndComments) : content + blockEndComments; + + return blockStart + possiblyIndentedContent + blockEnd; + } + + private String indent(String text) { + return indent(new StringBuilder(text)).toString(); + } + + private StringBuilder indent(StringBuilder stringBuilder) { + final String indentText = options.indentText; + + for (int i = 0; i < stringBuilder.length(); i++) { + char c = stringBuilder.charAt(i); + if (i == 0) { + stringBuilder.replace(i, i, indentText); + i += 2; + } + if (c == '\n') { + stringBuilder.replace(i, i + 1, "\n" + indentText); + i += 3; + } + } + return stringBuilder; + } + + /** + * Contains options that modify how a document is printed. + */ + public static class PrettyPrinterOptions { + private final String indentText; + private static final PrettyPrinterOptions defaultOptions = new PrettyPrinterOptions(IndentType.SPACE, 2); + + private PrettyPrinterOptions(IndentType indentType, int indentWidth) { + this.indentText = String.join("", Collections.nCopies(indentWidth, indentType.character)); + } + + public static Builder builder() { + return new Builder(); + } + + public enum IndentType { + TAB("\t"), SPACE(" "); + + private final String character; + + IndentType(String character) { + this.character = character; + } + } + + private static class Builder { + private IndentType indentType; + private int indentWidth = 1; + + public Builder indentType(IndentType indentType) { + this.indentType = indentType; + return this; + } + + public Builder indentWith(int indentWidth) { + this.indentWidth = indentWidth; + return this; + } + + public PrettyPrinterOptions build() { + return new PrettyPrinterOptions(indentType, indentWidth); + } + } + } +} diff --git a/src/main/java/graphql/parser/CommentParser.java b/src/main/java/graphql/parser/CommentParser.java new file mode 100644 index 0000000000..cb6e0f1a0d --- /dev/null +++ b/src/main/java/graphql/parser/CommentParser.java @@ -0,0 +1,247 @@ +package graphql.parser; + +import com.google.common.collect.ImmutableList; +import graphql.Internal; +import graphql.language.AbstractDescribedNode; +import graphql.language.Comment; +import graphql.language.Document; +import graphql.language.Node; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; + +/** + * Contains methods for extracting {@link Comment} in various positions within and around {@link Node}s + */ +@Internal +public class CommentParser { + private final Map, ParserRuleContext> nodeToRuleMap; + private CommonTokenStream tokens; + private static final int CHANNEL_COMMENTS = 2; + + public CommentParser(NodeToRuleCapturingParser.ParserContext parserContext) { + nodeToRuleMap = parserContext.getNodeToRuleMap(); + tokens = parserContext.getTokens(); + } + + /* + * type MyType { # beginning of type block + * field( # beginning of field args block + * arg1: String + * arg2: String + * ) + * } + */ + public Optional getBeginningOfBlockComment(Node node, String prefix) { + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + final Token start = ctx.start; + + if (start != null) { + return this.tokens.getTokens(start.getTokenIndex(), ctx.stop.getTokenIndex()).stream() + .filter(token -> token.getText().equals(prefix)) + .findFirst() + .map(token -> tokens.getHiddenTokensToRight(token.getTokenIndex(), CHANNEL_COMMENTS)) + .map(commentTokens -> getCommentOnChannel(commentTokens, isNotPrecededByLineBreak)) + .flatMap(comments -> comments.stream().findFirst()); + } + + return Optional.empty(); + } + + /* + * type MyType { + * a( + * arg1: A + * arg2: B + * # end of field args block comment + * ): A + * # end of type block comment #1 + * # end of type block comment #2 + * } + */ + public List getEndOfBlockComments(Node node, String blockSuffix) { + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + return searchTokenToLeft(ctx.stop, blockSuffix) + .map(suffixToken -> tokens.getHiddenTokensToLeft(suffixToken.getTokenIndex(), CHANNEL_COMMENTS)) + .map(commentTokens -> getCommentOnChannel(commentTokens, isPrecededByLineBreak)) + .orElse(Collections.emptyList()); + } + + /* + * type MyType { + * a: A # field trailing comment + * } # type trailing comment + */ + public Optional getTrailingComment(Node node) { + // Only nodes that can hold descriptions can have trailing comments + if (!(node instanceof AbstractDescribedNode)) { + return Optional.empty(); + } + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + List rightRefChannel = this.tokens.getHiddenTokensToRight(ctx.stop.getTokenIndex(), CHANNEL_COMMENTS); + + if (rightRefChannel != null) { + List comments = getCommentOnChannel(rightRefChannel, isNotPrecededByLineBreak); + + return comments.stream().findFirst(); + } + + return Optional.empty(); + } + + /* + * # type leading comment #1 + * # type leading comment #2 + * type MyType { + * # field leading comment #1 + * # field leading comment #2 + * a: A + * } + */ + public List getLeadingComments(Node node) { + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + final Token start = ctx.start; + if (start != null) { + int tokPos = start.getTokenIndex(); + List leftRefChannel = this.tokens.getHiddenTokensToLeft(tokPos, CHANNEL_COMMENTS); + if (leftRefChannel != null) { + return getCommentOnChannel(leftRefChannel, isPrecededByLineBreak); + } + } + + return Collections.emptyList(); + } + + /* + * """ Description """ + * # comment after description #1 + * # comment after description #2 + * type MyType { + * a: A + * } + */ + public List getCommentsAfterDescription(Node node) { + // Early return if node doesn't have a description + if (!(node instanceof AbstractDescribedNode) || + (node instanceof AbstractDescribedNode && ((AbstractDescribedNode) node).getDescription() == null) + ) { + return Collections.emptyList(); + } + + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + final Token start = ctx.start; + if (start != null) { + List commentTokens = tokens.getHiddenTokensToRight(start.getTokenIndex(), CHANNEL_COMMENTS); + + if (commentTokens != null) { + return getCommentOnChannel(commentTokens, isPrecededByLineBreak); + } + } + + return Collections.emptyList(); + } + + public Optional getCommentOnFirstLineOfDocument(Document node) { + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + final Token start = ctx.start; + if (start != null) { + int tokPos = start.getTokenIndex(); + List leftRefChannel = this.tokens.getHiddenTokensToLeft(tokPos, CHANNEL_COMMENTS); + if (leftRefChannel != null) { + List comments = getCommentOnChannel(leftRefChannel, isFirstToken.or(isPrecededOnlyBySpaces)); + + return comments.stream().findFirst(); + } + } + + return Optional.empty(); + } + + public List getCommentsAfterAllDefinitions(Document node) { + final ParserRuleContext ctx = nodeToRuleMap.get(node); + + final Token start = ctx.start; + if (start != null) { + List leftRefChannel = this.tokens.getHiddenTokensToRight(ctx.stop.getTokenIndex(), CHANNEL_COMMENTS); + if (leftRefChannel != null) { + return getCommentOnChannel(leftRefChannel, + refToken -> Optional.ofNullable(this.tokens.getHiddenTokensToLeft(refToken.getTokenIndex(), -1)) + .map(hiddenTokens -> hiddenTokens.stream().anyMatch(token -> token.getText().equals("\n"))) + .orElse(false) + ); + } + } + + return Collections.emptyList(); + } + + protected List getCommentOnChannel(List refChannel, Predicate shouldIncludePredicate) { + ImmutableList.Builder comments = ImmutableList.builder(); + for (Token refTok : refChannel) { + String text = refTok.getText(); + // we strip the leading hash # character but we don't trim because we don't + // know the "comment markup". Maybe it's space sensitive, maybe it's not. So + // consumers can decide that + if (text == null) { + continue; + } + + boolean shouldIncludeComment = shouldIncludePredicate.test(refTok); + + if (!shouldIncludeComment) { + continue; + } + + text = text.replaceFirst("^#", ""); + + comments.add(new Comment(text, null)); + } + return comments.build(); + } + + private Optional searchTokenToLeft(Token token, String text) { + int i = token.getTokenIndex(); + + while (i > 0) { + Token t = tokens.get(i); + if (t.getText().equals(text)) { + return Optional.of(t); + } + i--; + } + + return Optional.empty(); + } + + private final Predicate alwaysTrue = token -> true; + + private final Predicate isNotPrecededByLineBreak = refToken -> + Optional.ofNullable(tokens.getHiddenTokensToLeft(refToken.getTokenIndex(), -1)) + .map(hiddenTokens -> hiddenTokens.stream().noneMatch(token -> token.getText().equals("\n"))) + .orElse(false); + + private final Predicate isPrecededByLineBreak = refToken -> + Optional.ofNullable(this.tokens.getHiddenTokensToLeft(refToken.getTokenIndex(), -1)) + .map(hiddenTokens -> hiddenTokens.stream().anyMatch(token -> token.getText().equals("\n"))) + .orElse(false); + + private final Predicate isFirstToken = refToken -> refToken.getTokenIndex() == 0; + + private final Predicate isPrecededOnlyBySpaces = refToken -> + Optional.ofNullable(this.tokens.getTokens(0, refToken.getTokenIndex() - 1)) + .map(beforeTokens -> beforeTokens.stream().allMatch(token -> token.getText().equals(" "))) + .orElse(false); + +} diff --git a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java index 564d7983e6..e9743b1ab3 100644 --- a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java +++ b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java @@ -34,6 +34,7 @@ import graphql.language.InterfaceTypeDefinition; import graphql.language.InterfaceTypeExtensionDefinition; import graphql.language.ListType; +import graphql.language.Node; import graphql.language.NodeBuilder; import graphql.language.NonNullType; import graphql.language.NullValue; @@ -67,10 +68,12 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.TerminalNode; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static graphql.Assert.assertShouldNeverHappen; import static graphql.collect.ImmutableKit.emptyList; @@ -88,15 +91,21 @@ public class GraphqlAntlrToLanguage { private final MultiSourceReader multiSourceReader; private final ParserOptions parserOptions; + private final Map, ParserRuleContext> nodeToRuleMap; public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { this(tokens, multiSourceReader, null); } public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { + this(tokens, multiSourceReader, parserOptions, null); + } + + public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions, @Nullable Map, ParserRuleContext> nodeToRuleMap) { this.tokens = tokens; this.multiSourceReader = multiSourceReader; this.parserOptions = parserOptions == null ? ParserOptions.getDefaultParserOptions() : parserOptions; + this.nodeToRuleMap = nodeToRuleMap; } public ParserOptions getParserOptions() { @@ -110,7 +119,7 @@ public Document createDocument(GraphqlParser.DocumentContext ctx) { Document.Builder document = Document.newDocument(); addCommonData(document, ctx); document.definitions(map(ctx.definition(), this::createDefinition)); - return document.build(); + return addToMap(document.build(), ctx); } protected Definition createDefinition(GraphqlParser.DefinitionContext definitionContext) { @@ -141,7 +150,7 @@ protected OperationDefinition createOperationDefinition(GraphqlParser.OperationD operationDefinition.variableDefinitions(createVariableDefinitions(ctx.variableDefinitions())); operationDefinition.selectionSet(createSelectionSet(ctx.selectionSet())); operationDefinition.directives(createDirectives(ctx.directives())); - return operationDefinition.build(); + return addToMap(operationDefinition.build(), ctx); } protected OperationDefinition.Operation parseOperation(GraphqlParser.OperationTypeContext operationTypeContext) { @@ -161,7 +170,7 @@ protected FragmentSpread createFragmentSpread(GraphqlParser.FragmentSpreadContex FragmentSpread.Builder fragmentSpread = FragmentSpread.newFragmentSpread().name(ctx.fragmentName().getText()); addCommonData(fragmentSpread, ctx); fragmentSpread.directives(createDirectives(ctx.directives())); - return fragmentSpread.build(); + return addToMap(fragmentSpread.build(), ctx); } protected List createVariableDefinitions(GraphqlParser.VariableDefinitionsContext ctx) { @@ -181,7 +190,7 @@ protected VariableDefinition createVariableDefinition(GraphqlParser.VariableDefi } variableDefinition.type(createType(ctx.type())); variableDefinition.directives(createDirectives(ctx.directives())); - return variableDefinition.build(); + return addToMap(variableDefinition.build(), ctx); } @@ -192,7 +201,7 @@ protected FragmentDefinition createFragmentDefinition(GraphqlParser.FragmentDefi fragmentDefinition.typeCondition(TypeName.newTypeName().name(ctx.typeCondition().typeName().getText()).build()); fragmentDefinition.directives(createDirectives(ctx.directives())); fragmentDefinition.selectionSet(createSelectionSet(ctx.selectionSet())); - return fragmentDefinition.build(); + return addToMap(fragmentDefinition.build(), ctx); } @@ -216,7 +225,7 @@ protected SelectionSet createSelectionSet(GraphqlParser.SelectionSetContext ctx) }); builder.selections(selections); - return builder.build(); + return addToMap(builder.build(), ctx); } @@ -231,7 +240,7 @@ protected Field createField(GraphqlParser.FieldContext ctx) { builder.directives(createDirectives(ctx.directives())); builder.arguments(createArguments(ctx.arguments())); builder.selectionSet(createSelectionSet(ctx.selectionSet())); - return builder.build(); + return addToMap(builder.build(), ctx); } @@ -243,7 +252,7 @@ protected InlineFragment createInlineFragment(GraphqlParser.InlineFragmentContex } inlineFragment.directives(createDirectives(ctx.directives())); inlineFragment.selectionSet(createSelectionSet(ctx.selectionSet())); - return inlineFragment.build(); + return addToMap(inlineFragment.build(), ctx); } //MARKER END: Here GraphqlOperation.g4 specific methods end @@ -334,7 +343,7 @@ protected TypeName createTypeName(GraphqlParser.TypeNameContext ctx) { TypeName.Builder builder = TypeName.newTypeName(); builder.name(ctx.name().getText()); addCommonData(builder, ctx); - return builder.build(); + return addToMap(builder.build(), ctx); } protected NonNullType createNonNullType(GraphqlParser.NonNullTypeContext ctx) { @@ -347,14 +356,14 @@ protected NonNullType createNonNullType(GraphqlParser.NonNullTypeContext ctx) { } else { return assertShouldNeverHappen(); } - return builder.build(); + return addToMap(builder.build(), ctx); } protected ListType createListType(GraphqlParser.ListTypeContext ctx) { ListType.Builder builder = ListType.newListType(); addCommonData(builder, ctx); builder.type(createType(ctx.type())); - return builder.build(); + return addToMap(builder.build(), ctx); } protected Argument createArgument(GraphqlParser.ArgumentContext ctx) { @@ -362,7 +371,7 @@ protected Argument createArgument(GraphqlParser.ArgumentContext ctx) { addCommonData(builder, ctx); builder.name(ctx.name().getText()); builder.value(createValue(ctx.valueWithVariable())); - return builder.build(); + return addToMap(builder.build(), ctx); } protected List createArguments(GraphqlParser.ArgumentsContext ctx) { @@ -385,7 +394,7 @@ protected Directive createDirective(GraphqlParser.DirectiveContext ctx) { builder.name(ctx.name().getText()); addCommonData(builder, ctx); builder.arguments(createArguments(ctx.arguments())); - return builder.build(); + return addToMap(builder.build(), ctx); } protected SchemaDefinition createSchemaDefinition(GraphqlParser.SchemaDefinitionContext ctx) { @@ -394,7 +403,7 @@ protected SchemaDefinition createSchemaDefinition(GraphqlParser.SchemaDefinition def.directives(createDirectives(ctx.directives())); def.description(newDescription(ctx.description())); def.operationTypeDefinitions(map(ctx.operationTypeDefinition(), this::createOperationTypeDefinition)); - return def.build(); + return addToMap(def.build(), ctx); } private SDLDefinition creationSchemaExtension(GraphqlParser.SchemaExtensionContext ctx) { @@ -410,7 +419,7 @@ private SDLDefinition creationSchemaExtension(GraphqlParser.SchemaExtensionConte List operationTypeDefs = map(ctx.operationTypeDefinition(), this::createOperationTypeDefinition); def.operationTypeDefinitions(operationTypeDefs); - return def.build(); + return addToMap(def.build(), ctx); } @@ -419,7 +428,7 @@ protected OperationTypeDefinition createOperationTypeDefinition(GraphqlParser.Op def.name(ctx.operationType().getText()); def.typeName(createTypeName(ctx.typeName())); addCommonData(def, ctx); - return def.build(); + return addToMap(def.build(), ctx); } protected ScalarTypeDefinition createScalarTypeDefinition(GraphqlParser.ScalarTypeDefinitionContext ctx) { @@ -428,7 +437,7 @@ protected ScalarTypeDefinition createScalarTypeDefinition(GraphqlParser.ScalarTy addCommonData(def, ctx); def.description(newDescription(ctx.description())); def.directives(createDirectives(ctx.directives())); - return def.build(); + return addToMap(def.build(), ctx); } protected ScalarTypeExtensionDefinition createScalarTypeExtensionDefinition(GraphqlParser.ScalarTypeExtensionDefinitionContext ctx) { @@ -436,7 +445,7 @@ protected ScalarTypeExtensionDefinition createScalarTypeExtensionDefinition(Grap def.name(ctx.name().getText()); addCommonData(def, ctx); def.directives(createDirectives(ctx.directives())); - return def.build(); + return addToMap(def.build(), ctx); } protected ObjectTypeDefinition createObjectTypeDefinition(GraphqlParser.ObjectTypeDefinitionContext ctx) { @@ -451,7 +460,7 @@ protected ObjectTypeDefinition createObjectTypeDefinition(GraphqlParser.ObjectTy if (ctx.fieldsDefinition() != null) { def.fieldDefinitions(createFieldDefinitions(ctx.fieldsDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected ObjectTypeExtensionDefinition createObjectTypeExtensionDefinition(GraphqlParser.ObjectTypeExtensionDefinitionContext ctx) { @@ -465,7 +474,7 @@ protected ObjectTypeExtensionDefinition createObjectTypeExtensionDefinition(Grap if (ctx.extensionFieldsDefinition() != null) { def.fieldDefinitions(createFieldDefinitions(ctx.extensionFieldsDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected List createFieldDefinitions(GraphqlParser.FieldsDefinitionContext ctx) { @@ -493,7 +502,7 @@ protected FieldDefinition createFieldDefinition(GraphqlParser.FieldDefinitionCon if (ctx.argumentsDefinition() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.argumentsDefinition().inputValueDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected List createInputValueDefinitions(List defs) { @@ -510,7 +519,7 @@ protected InputValueDefinition createInputValueDefinition(GraphqlParser.InputVal def.defaultValue(createValue(ctx.defaultValue().value())); } def.directives(createDirectives(ctx.directives())); - return def.build(); + return addToMap(def.build(), ctx); } protected InterfaceTypeDefinition createInterfaceTypeDefinition(GraphqlParser.InterfaceTypeDefinitionContext ctx) { @@ -523,7 +532,7 @@ protected InterfaceTypeDefinition createInterfaceTypeDefinition(GraphqlParser.In List implementz = getImplementz(implementsInterfacesContext); def.implementz(implementz); def.definitions(createFieldDefinitions(ctx.fieldsDefinition())); - return def.build(); + return addToMap(def.build(), ctx); } protected InterfaceTypeExtensionDefinition createInterfaceTypeExtensionDefinition(GraphqlParser.InterfaceTypeExtensionDefinitionContext ctx) { @@ -535,7 +544,7 @@ protected InterfaceTypeExtensionDefinition createInterfaceTypeExtensionDefinitio List implementz = getImplementz(implementsInterfacesContext); def.implementz(implementz); def.definitions(createFieldDefinitions(ctx.extensionFieldsDefinition())); - return def.build(); + return addToMap(def.build(), ctx); } protected UnionTypeDefinition createUnionTypeDefinition(GraphqlParser.UnionTypeDefinitionContext ctx) { @@ -554,7 +563,7 @@ protected UnionTypeDefinition createUnionTypeDefinition(GraphqlParser.UnionTypeD } } def.memberTypes(members); - return def.build(); + return addToMap(def.build(), ctx); } protected UnionTypeExtensionDefinition createUnionTypeExtensionDefinition(GraphqlParser.UnionTypeExtensionDefinitionContext ctx) { @@ -571,7 +580,7 @@ protected UnionTypeExtensionDefinition createUnionTypeExtensionDefinition(Graphq } def.memberTypes(members); } - return def.build(); + return addToMap(def.build(), ctx); } protected EnumTypeDefinition createEnumTypeDefinition(GraphqlParser.EnumTypeDefinitionContext ctx) { @@ -584,7 +593,7 @@ protected EnumTypeDefinition createEnumTypeDefinition(GraphqlParser.EnumTypeDefi def.enumValueDefinitions( map(ctx.enumValueDefinitions().enumValueDefinition(), this::createEnumValueDefinition)); } - return def.build(); + return addToMap(def.build(), ctx); } protected EnumTypeExtensionDefinition createEnumTypeExtensionDefinition(GraphqlParser.EnumTypeExtensionDefinitionContext ctx) { @@ -596,7 +605,7 @@ protected EnumTypeExtensionDefinition createEnumTypeExtensionDefinition(GraphqlP def.enumValueDefinitions( map(ctx.extensionEnumValueDefinitions().enumValueDefinition(), this::createEnumValueDefinition)); } - return def.build(); + return addToMap(def.build(), ctx); } protected EnumValueDefinition createEnumValueDefinition(GraphqlParser.EnumValueDefinitionContext ctx) { @@ -605,7 +614,7 @@ protected EnumValueDefinition createEnumValueDefinition(GraphqlParser.EnumValueD addCommonData(def, ctx); def.description(newDescription(ctx.description())); def.directives(createDirectives(ctx.directives())); - return def.build(); + return addToMap(def.build(), ctx); } protected InputObjectTypeDefinition createInputObjectTypeDefinition(GraphqlParser.InputObjectTypeDefinitionContext ctx) { @@ -617,7 +626,7 @@ protected InputObjectTypeDefinition createInputObjectTypeDefinition(GraphqlParse if (ctx.inputObjectValueDefinitions() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.inputObjectValueDefinitions().inputValueDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected InputObjectTypeExtensionDefinition createInputObjectTypeExtensionDefinition(GraphqlParser.InputObjectTypeExtensionDefinitionContext ctx) { @@ -628,7 +637,7 @@ protected InputObjectTypeExtensionDefinition createInputObjectTypeExtensionDefin if (ctx.extensionInputObjectValueDefinitions() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.extensionInputObjectValueDefinitions().inputValueDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected DirectiveDefinition createDirectiveDefinition(GraphqlParser.DirectiveDefinitionContext ctx) { @@ -649,41 +658,41 @@ protected DirectiveDefinition createDirectiveDefinition(GraphqlParser.DirectiveD if (ctx.argumentsDefinition() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.argumentsDefinition().inputValueDefinition())); } - return def.build(); + return addToMap(def.build(), ctx); } protected DirectiveLocation createDirectiveLocation(GraphqlParser.DirectiveLocationContext ctx) { DirectiveLocation.Builder def = DirectiveLocation.newDirectiveLocation(); def.name(ctx.name().getText()); addCommonData(def, ctx); - return def.build(); + return addToMap(def.build(), ctx); } protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { if (ctx.IntValue() != null) { IntValue.Builder intValue = IntValue.newIntValue().value(new BigInteger(ctx.IntValue().getText())); addCommonData(intValue, ctx); - return intValue.build(); + return addToMap(intValue.build(), ctx); } else if (ctx.FloatValue() != null) { FloatValue.Builder floatValue = FloatValue.newFloatValue().value(new BigDecimal(ctx.FloatValue().getText())); addCommonData(floatValue, ctx); - return floatValue.build(); + return addToMap(floatValue.build(), ctx); } else if (ctx.BooleanValue() != null) { BooleanValue.Builder booleanValue = BooleanValue.newBooleanValue().value(Boolean.parseBoolean(ctx.BooleanValue().getText())); addCommonData(booleanValue, ctx); - return booleanValue.build(); + return addToMap(booleanValue.build(), ctx); } else if (ctx.NullValue() != null) { NullValue.Builder nullValue = NullValue.newNullValue(); addCommonData(nullValue, ctx); - return nullValue.build(); + return addToMap(nullValue.build(), ctx); } else if (ctx.StringValue() != null) { StringValue.Builder stringValue = StringValue.newStringValue().value(quotedString(ctx.StringValue())); addCommonData(stringValue, ctx); - return stringValue.build(); + return addToMap(stringValue.build(), ctx); } else if (ctx.enumValue() != null) { EnumValue.Builder enumValue = EnumValue.newEnumValue().name(ctx.enumValue().getText()); addCommonData(enumValue, ctx); - return enumValue.build(); + return addToMap(enumValue.build(), ctx); } else if (ctx.arrayValueWithVariable() != null) { ArrayValue.Builder arrayValue = ArrayValue.newArrayValue(); addCommonData(arrayValue, ctx); @@ -691,7 +700,7 @@ protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { for (GraphqlParser.ValueWithVariableContext valueWithVariableContext : ctx.arrayValueWithVariable().valueWithVariable()) { values.add(createValue(valueWithVariableContext)); } - return arrayValue.values(values).build(); + return addToMap(arrayValue.values(values).build(), ctx); } else if (ctx.objectValueWithVariable() != null) { ObjectValue.Builder objectValue = ObjectValue.newObjectValue(); addCommonData(objectValue, ctx); @@ -705,11 +714,11 @@ protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { .build(); objectFields.add(objectField); } - return objectValue.objectFields(objectFields).build(); + return addToMap(objectValue.objectFields(objectFields).build(), ctx); } else if (ctx.variable() != null) { VariableReference.Builder variableReference = VariableReference.newVariableReference().name(ctx.variable().name().getText()); addCommonData(variableReference, ctx); - return variableReference.build(); + return addToMap(variableReference.build(), ctx); } return assertShouldNeverHappen(); } @@ -718,27 +727,27 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { if (ctx.IntValue() != null) { IntValue.Builder intValue = IntValue.newIntValue().value(new BigInteger(ctx.IntValue().getText())); addCommonData(intValue, ctx); - return intValue.build(); + return addToMap(intValue.build(), ctx); } else if (ctx.FloatValue() != null) { FloatValue.Builder floatValue = FloatValue.newFloatValue().value(new BigDecimal(ctx.FloatValue().getText())); addCommonData(floatValue, ctx); - return floatValue.build(); + return addToMap(floatValue.build(), ctx); } else if (ctx.BooleanValue() != null) { BooleanValue.Builder booleanValue = BooleanValue.newBooleanValue().value(Boolean.parseBoolean(ctx.BooleanValue().getText())); addCommonData(booleanValue, ctx); - return booleanValue.build(); + return addToMap(booleanValue.build(), ctx); } else if (ctx.NullValue() != null) { NullValue.Builder nullValue = NullValue.newNullValue(); addCommonData(nullValue, ctx); - return nullValue.build(); + return addToMap(nullValue.build(), ctx); } else if (ctx.StringValue() != null) { StringValue.Builder stringValue = StringValue.newStringValue().value(quotedString(ctx.StringValue())); addCommonData(stringValue, ctx); - return stringValue.build(); + return addToMap(stringValue.build(), ctx); } else if (ctx.enumValue() != null) { EnumValue.Builder enumValue = EnumValue.newEnumValue().name(ctx.enumValue().getText()); addCommonData(enumValue, ctx); - return enumValue.build(); + return addToMap(enumValue.build(), ctx); } else if (ctx.arrayValue() != null) { ArrayValue.Builder arrayValue = ArrayValue.newArrayValue(); addCommonData(arrayValue, ctx); @@ -746,7 +755,7 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { for (GraphqlParser.ValueContext valueContext : ctx.arrayValue().value()) { values.add(createValue(valueContext)); } - return arrayValue.values(values).build(); + return addToMap(arrayValue.values(values).build(), ctx); } else if (ctx.objectValue() != null) { ObjectValue.Builder objectValue = ObjectValue.newObjectValue(); addCommonData(objectValue, ctx); @@ -759,7 +768,7 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { .build(); objectFields.add(objectField); } - return objectValue.objectFields(objectFields).build(); + return addToMap(objectValue.objectFields(objectFields).build(), ctx); } return assertShouldNeverHappen(); } @@ -803,7 +812,7 @@ private void addIgnoredChars(ParserRuleContext ctx, NodeBuilder nodeBuilder) { private List mapTokenToIgnoredChar(List tokens) { if (tokens == null) { - return ImmutableKit.emptyList(); + return emptyList(); } return map(tokens, this::createIgnoredChar); @@ -917,4 +926,11 @@ private List getImplementz(GraphqlParser.ImplementsInterfacesContext imple } return implementz; } + + private > T addToMap(T node, ParserRuleContext ctx) { + if (nodeToRuleMap != null) { + nodeToRuleMap.put(node, ctx); + } + return node; + } } diff --git a/src/main/java/graphql/parser/NodeToRuleCapturingParser.java b/src/main/java/graphql/parser/NodeToRuleCapturingParser.java new file mode 100644 index 0000000000..88ffc94be6 --- /dev/null +++ b/src/main/java/graphql/parser/NodeToRuleCapturingParser.java @@ -0,0 +1,49 @@ +package graphql.parser; + +import graphql.Internal; +import graphql.language.Node; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * A parser that will capture parsing context data which can be later used for accessing tokens that are discarded + * during the conventional parsing process (like comments). + */ +@Internal +public class NodeToRuleCapturingParser extends Parser { + private final ParserContext parserContext; + + public NodeToRuleCapturingParser() { + parserContext = new ParserContext(); + } + + @Override + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { + parserContext.tokens = tokens; + return new GraphqlAntlrToLanguage(tokens, multiSourceReader, parserOptions, parserContext.nodeToRuleMap); + } + + public ParserContext getParserContext() { + return parserContext; + } + + static public class ParserContext { + private final Map, ParserRuleContext> nodeToRuleMap; + private CommonTokenStream tokens; + + public ParserContext() { + this.nodeToRuleMap = new HashMap<>(); + } + + protected CommonTokenStream getTokens() { + return tokens; + } + + protected Map, ParserRuleContext> getNodeToRuleMap() { + return nodeToRuleMap; + } + } +} diff --git a/src/test/groovy/graphql/language/PrettyPrinterTest.groovy b/src/test/groovy/graphql/language/PrettyPrinterTest.groovy new file mode 100644 index 0000000000..83922d0769 --- /dev/null +++ b/src/test/groovy/graphql/language/PrettyPrinterTest.groovy @@ -0,0 +1,529 @@ +package graphql.language + +import graphql.parser.NodeToRuleCapturingParser +import spock.lang.Specification + +class PrettyPrinterTest extends Specification { + + def "can print type with comments"() { + given: + def input = ''' +# before description +""" Description """ +# before def #1 +# before def #2 +type Query { # beginning of block +a: A b: B + # end of block inside #1 +# end of block inside #2 +} # end of block +''' + + def expected = '''# before description +""" + Description +""" +# before def #1 +# before def #2 +type Query { # beginning of block + a: A + b: B + # end of block inside #1 + # end of block inside #2 +} # end of block +''' + when: + def result = print(input) + + then: + result == expected + } + + + def "can print type with no comments"() { + given: + def input = ''' +""" Description """ +type Query { +a: A b: B} +''' + + def expected = '''""" + Description +""" +type Query { + a: A + b: B +} +''' + when: + def result = print(input) + then: + result == expected + } + + + def "can print interface with comments"() { + given: + def input = ''' +# before description +""" Description """ +# before def #1 +# before def #2 +interface MyInterface { # beginning of block +a: A b: B} # end of block +''' + + def expected = '''# before description +""" + Description +""" +# before def #1 +# before def #2 +interface MyInterface { # beginning of block + a: A + b: B +} # end of block +''' + when: + def result = print(input) + + then: + result == expected + } + + + def "can print interface with no comments"() { + given: + def input = ''' +""" Description """ +interface MyInterface { +a: A b: B} +''' + + def expected = '''""" + Description +""" +interface MyInterface { + a: A + b: B +} +''' + when: + def result = print(input) + then: + result == expected + } + + + def "can print fields with comments"() { + given: + def input = ''' +# before type +type MyType { # beginning of block + +# before field A #1 + +# before field A #2 +""" Description fieldA """ +# before field A #3 + a(arg1: String, arg2: String, arg3: String): A # after field A + # before field B #1 +b(arg1: String, arg2: String, arg3: String): B # after field B +} # end of block +''' + + def expected = '''# before type +type MyType { # beginning of block + # before field A #1 + # before field A #2 + """ + Description fieldA + """ + # before field A #3 + a(arg1: String, arg2: String, arg3: String): A # after field A + # before field B #1 + b(arg1: String, arg2: String, arg3: String): B # after field B +} # end of block +''' + when: + def result = print(input) + + then: + result == expected + } + + + def "can print field arguments with comments"() { + given: + def input = '''type MyType { +""" Description fieldA """ + a(arg1: String # arg1 #1 + # arg2 #1 + """ Description arg2 """ + # arg2 #2 + arg2: String, arg3: String # arg3 #1 + # after all args + ): A # after field A +} +''' + + def expected = '''type MyType { + """ + Description fieldA + """ + a( + arg1: String # arg1 #1 + # arg2 #1 + """ + Description arg2 + """ + # arg2 #2 + arg2: String + arg3: String # arg3 #1 + # after all args + ): A # after field A +} +''' + when: + def result = print(input) + + then: + result == expected + } + + + def "can print schema keeping document level comments"() { + given: + def input = ''' # start of document + +# before Query def + +type Query {a: A b: B} +type MyType {field(id: ID!): MyType! listField: [MyType!]!} +enum MyEnum {VALUE_1 VALUE_2} +# end of document #1 +# end of document #2 +''' + + def expected = """# start of document +# before Query def +type Query { + a: A + b: B +} + +type MyType { + field(id: ID!): MyType! + listField: [MyType!]! +} + +enum MyEnum { + VALUE_1 + VALUE_2 +} +# end of document #1 +# end of document #2 +""" + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print comments between implements"() { + given: + def input = ''' +type MyType implements A & +# interface B #1 + # interface B #2 + B + & + # interface C + C {a: A} +''' + + def expected = '''type MyType implements + A & + # interface B #1 + # interface B #2 + B & + # interface C + C + { + a: A +} +''' + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print type implementing interfaces with no comments"() { + given: + def input = ''' +type MyType implements A & + B & + C + & D + & E { # trailing comment + a: A} +''' + + def expected = '''type MyType implements A & B & C & D & E { # trailing comment + a: A +} +''' + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print enums without comments"() { + given: + def input = ''' +enum MyEnum { + VALUE_1 +VALUE_2 VALUE_3 +} +''' + + def expected = '''enum MyEnum { + VALUE_1 + VALUE_2 + VALUE_3 +} +''' + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print comments in enums"() { + given: + def input = ''' +# before def +enum MyEnum { # inside block #1 +# before VALUE_1 #1 + """ VALUE_1 description """ + # before VALUE_1 #2 + VALUE_1 # after VALUE_1 +VALUE_2 + #inside block #2 +} +''' + + def expected = '''# before def +enum MyEnum { # inside block #1 + # before VALUE_1 #1 + """ + VALUE_1 description + """ + # before VALUE_1 #2 + VALUE_1 # after VALUE_1 + VALUE_2 + #inside block #2 +} +''' + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print comments in scalars"() { + given: + def input = ''' +# before def #1 +""" Description """ + # before def #2 +scalar +MyScalar # def trailing +# after def +''' + + def expected = '''# before def #1 +""" + Description +""" +# before def #2 +scalar MyScalar # def trailing +# after def +''' + + when: + def result = print(input) + then: + + result == expected + } + + + def "can print comments in directives"() { + given: + def input = ''' +# before def #1 +""" Description def """ + # before def #2 +directive +@myDirective( # inside block #1 + # arg1 #1 + """ Description arg1 """ + + # arg1 #2 + arg1: String! # arg1 trailing + # arg2 #1 + arg2: ID arg3: String! # arg3 trailing + # inside block #1 + ) on FIELD_DEFINITION # trailing def +# after def +''' + + def expected = '''# before def #1 +""" + Description def +""" +# before def #2 +directive @myDirective( # inside block #1 + # arg1 #1 + """ + Description arg1 + """ + # arg1 #2 + arg1: String! # arg1 trailing + # arg2 #1 + arg2: ID + arg3: String! # arg3 trailing + # inside block #1 +) on FIELD_DEFINITION # trailing def +# after def +''' + + when: + def result = print(input) + then: + + result == expected + } + + def "can print extended type with comments"() { + given: + def input = ''' +# before description +extend type Query { # beginning of block +a: A b: B + # end of block inside #1 +# end of block inside #2 +} # end of block +''' + + def expected = '''# before description +extend type Query { # beginning of block + a: A + b: B + # end of block inside #1 + # end of block inside #2 +} # end of block +''' + when: + def result = print(input) + + then: + result == expected + } + + + def "can print input type with comments"() { + given: + def input = ''' +# before description +""" Description """ +# before def #1 +# before def #2 +input MyInput { # beginning of block +a: A b: B + # end of block inside #1 +# end of block inside #2 +} # end of block +''' + + def expected = '''# before description +""" + Description +""" +# before def #1 +# before def #2 +input MyInput { # beginning of block + a: A + b: B + # end of block inside #1 + # end of block inside #2 +} # end of block +''' + when: + def result = print(input) + + then: + result == expected + } + + def "can use print with tab indent"() { + given: + def input = ''' +type Query { +field( +# comment +a: A, b: B): Type +} +''' + + def expected = '''type Query { +\tfield( +\t\t# comment +\t\ta: A +\t\tb: B +\t): Type +} +''' + when: + def options = PrettyPrinter.PrettyPrinterOptions + .builder() + .indentType(PrettyPrinter.PrettyPrinterOptions.IndentType.TAB) + .indentWith(1) + .build() + + def parser = new NodeToRuleCapturingParser() + def document = parser.parseDocument(input) + + def result = new PrettyPrinter(parser.parserContext, options).print(document) + + then: + result == expected + } + + private static String print(String input) { + def parser = new NodeToRuleCapturingParser() + def document = parser.parseDocument(input) + def parserContext = parser.parserContext + + return new PrettyPrinter(parserContext).print(document) + } +} From 05c229d186402784bffdfe9909059d24374481e8 Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Wed, 3 Aug 2022 12:04:10 +1000 Subject: [PATCH 064/294] Expose default pretty options and rename capture rule context method --- .../java/graphql/language/PrettyPrinter.java | 6 +- .../parser/GraphqlAntlrToLanguage.java | 102 +++++++++--------- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/main/java/graphql/language/PrettyPrinter.java b/src/main/java/graphql/language/PrettyPrinter.java index 81ebe16fc9..c8a680f164 100644 --- a/src/main/java/graphql/language/PrettyPrinter.java +++ b/src/main/java/graphql/language/PrettyPrinter.java @@ -422,6 +422,10 @@ private PrettyPrinterOptions(IndentType indentType, int indentWidth) { this.indentText = String.join("", Collections.nCopies(indentWidth, indentType.character)); } + public static PrettyPrinterOptions defaultOptions() { + return defaultOptions; + } + public static Builder builder() { return new Builder(); } @@ -436,7 +440,7 @@ public enum IndentType { } } - private static class Builder { + public static class Builder { private IndentType indentType; private int indentWidth = 1; diff --git a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java index ca71c0c380..012da493f2 100644 --- a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java +++ b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java @@ -120,7 +120,7 @@ public Document createDocument(GraphqlParser.DocumentContext ctx) { Document.Builder document = Document.newDocument(); addCommonData(document, ctx); document.definitions(map(ctx.definition(), this::createDefinition)); - return addToMap(document.build(), ctx); + return captureRuleContext(document.build(), ctx); } protected Definition createDefinition(GraphqlParser.DefinitionContext definitionContext) { @@ -151,7 +151,7 @@ protected OperationDefinition createOperationDefinition(GraphqlParser.OperationD operationDefinition.variableDefinitions(createVariableDefinitions(ctx.variableDefinitions())); operationDefinition.selectionSet(createSelectionSet(ctx.selectionSet())); operationDefinition.directives(createDirectives(ctx.directives())); - return addToMap(operationDefinition.build(), ctx); + return captureRuleContext(operationDefinition.build(), ctx); } protected OperationDefinition.Operation parseOperation(GraphqlParser.OperationTypeContext operationTypeContext) { @@ -171,7 +171,7 @@ protected FragmentSpread createFragmentSpread(GraphqlParser.FragmentSpreadContex FragmentSpread.Builder fragmentSpread = FragmentSpread.newFragmentSpread().name(ctx.fragmentName().getText()); addCommonData(fragmentSpread, ctx); fragmentSpread.directives(createDirectives(ctx.directives())); - return addToMap(fragmentSpread.build(), ctx); + return captureRuleContext(fragmentSpread.build(), ctx); } protected List createVariableDefinitions(GraphqlParser.VariableDefinitionsContext ctx) { @@ -191,7 +191,7 @@ protected VariableDefinition createVariableDefinition(GraphqlParser.VariableDefi } variableDefinition.type(createType(ctx.type())); variableDefinition.directives(createDirectives(ctx.directives())); - return addToMap(variableDefinition.build(), ctx); + return captureRuleContext(variableDefinition.build(), ctx); } @@ -202,7 +202,7 @@ protected FragmentDefinition createFragmentDefinition(GraphqlParser.FragmentDefi fragmentDefinition.typeCondition(TypeName.newTypeName().name(ctx.typeCondition().typeName().getText()).build()); fragmentDefinition.directives(createDirectives(ctx.directives())); fragmentDefinition.selectionSet(createSelectionSet(ctx.selectionSet())); - return addToMap(fragmentDefinition.build(), ctx); + return captureRuleContext(fragmentDefinition.build(), ctx); } @@ -226,7 +226,7 @@ protected SelectionSet createSelectionSet(GraphqlParser.SelectionSetContext ctx) }); builder.selections(selections); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } @@ -241,7 +241,7 @@ protected Field createField(GraphqlParser.FieldContext ctx) { builder.directives(createDirectives(ctx.directives())); builder.arguments(createArguments(ctx.arguments())); builder.selectionSet(createSelectionSet(ctx.selectionSet())); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } @@ -253,7 +253,7 @@ protected InlineFragment createInlineFragment(GraphqlParser.InlineFragmentContex } inlineFragment.directives(createDirectives(ctx.directives())); inlineFragment.selectionSet(createSelectionSet(ctx.selectionSet())); - return addToMap(inlineFragment.build(), ctx); + return captureRuleContext(inlineFragment.build(), ctx); } //MARKER END: Here GraphqlOperation.g4 specific methods end @@ -344,7 +344,7 @@ protected TypeName createTypeName(GraphqlParser.TypeNameContext ctx) { TypeName.Builder builder = TypeName.newTypeName(); builder.name(ctx.name().getText()); addCommonData(builder, ctx); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } protected NonNullType createNonNullType(GraphqlParser.NonNullTypeContext ctx) { @@ -357,14 +357,14 @@ protected NonNullType createNonNullType(GraphqlParser.NonNullTypeContext ctx) { } else { return assertShouldNeverHappen(); } - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } protected ListType createListType(GraphqlParser.ListTypeContext ctx) { ListType.Builder builder = ListType.newListType(); addCommonData(builder, ctx); builder.type(createType(ctx.type())); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } protected Argument createArgument(GraphqlParser.ArgumentContext ctx) { @@ -372,7 +372,7 @@ protected Argument createArgument(GraphqlParser.ArgumentContext ctx) { addCommonData(builder, ctx); builder.name(ctx.name().getText()); builder.value(createValue(ctx.valueWithVariable())); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } protected List createArguments(GraphqlParser.ArgumentsContext ctx) { @@ -395,7 +395,7 @@ protected Directive createDirective(GraphqlParser.DirectiveContext ctx) { builder.name(ctx.name().getText()); addCommonData(builder, ctx); builder.arguments(createArguments(ctx.arguments())); - return addToMap(builder.build(), ctx); + return captureRuleContext(builder.build(), ctx); } protected SchemaDefinition createSchemaDefinition(GraphqlParser.SchemaDefinitionContext ctx) { @@ -404,7 +404,7 @@ protected SchemaDefinition createSchemaDefinition(GraphqlParser.SchemaDefinition def.directives(createDirectives(ctx.directives())); def.description(newDescription(ctx.description())); def.operationTypeDefinitions(map(ctx.operationTypeDefinition(), this::createOperationTypeDefinition)); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } private SDLDefinition creationSchemaExtension(GraphqlParser.SchemaExtensionContext ctx) { @@ -420,7 +420,7 @@ private SDLDefinition creationSchemaExtension(GraphqlParser.SchemaExtensionConte List operationTypeDefs = map(ctx.operationTypeDefinition(), this::createOperationTypeDefinition); def.operationTypeDefinitions(operationTypeDefs); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } @@ -429,7 +429,7 @@ protected OperationTypeDefinition createOperationTypeDefinition(GraphqlParser.Op def.name(ctx.operationType().getText()); def.typeName(createTypeName(ctx.typeName())); addCommonData(def, ctx); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected ScalarTypeDefinition createScalarTypeDefinition(GraphqlParser.ScalarTypeDefinitionContext ctx) { @@ -438,7 +438,7 @@ protected ScalarTypeDefinition createScalarTypeDefinition(GraphqlParser.ScalarTy addCommonData(def, ctx); def.description(newDescription(ctx.description())); def.directives(createDirectives(ctx.directives())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected ScalarTypeExtensionDefinition createScalarTypeExtensionDefinition(GraphqlParser.ScalarTypeExtensionDefinitionContext ctx) { @@ -446,7 +446,7 @@ protected ScalarTypeExtensionDefinition createScalarTypeExtensionDefinition(Grap def.name(ctx.name().getText()); addCommonData(def, ctx); def.directives(createDirectives(ctx.directives())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected ObjectTypeDefinition createObjectTypeDefinition(GraphqlParser.ObjectTypeDefinitionContext ctx) { @@ -461,7 +461,7 @@ protected ObjectTypeDefinition createObjectTypeDefinition(GraphqlParser.ObjectTy if (ctx.fieldsDefinition() != null) { def.fieldDefinitions(createFieldDefinitions(ctx.fieldsDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected ObjectTypeExtensionDefinition createObjectTypeExtensionDefinition(GraphqlParser.ObjectTypeExtensionDefinitionContext ctx) { @@ -475,7 +475,7 @@ protected ObjectTypeExtensionDefinition createObjectTypeExtensionDefinition(Grap if (ctx.extensionFieldsDefinition() != null) { def.fieldDefinitions(createFieldDefinitions(ctx.extensionFieldsDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected List createFieldDefinitions(GraphqlParser.FieldsDefinitionContext ctx) { @@ -503,7 +503,7 @@ protected FieldDefinition createFieldDefinition(GraphqlParser.FieldDefinitionCon if (ctx.argumentsDefinition() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.argumentsDefinition().inputValueDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected List createInputValueDefinitions(List defs) { @@ -520,7 +520,7 @@ protected InputValueDefinition createInputValueDefinition(GraphqlParser.InputVal def.defaultValue(createValue(ctx.defaultValue().value())); } def.directives(createDirectives(ctx.directives())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected InterfaceTypeDefinition createInterfaceTypeDefinition(GraphqlParser.InterfaceTypeDefinitionContext ctx) { @@ -533,7 +533,7 @@ protected InterfaceTypeDefinition createInterfaceTypeDefinition(GraphqlParser.In List implementz = getImplementz(implementsInterfacesContext); def.implementz(implementz); def.definitions(createFieldDefinitions(ctx.fieldsDefinition())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected InterfaceTypeExtensionDefinition createInterfaceTypeExtensionDefinition(GraphqlParser.InterfaceTypeExtensionDefinitionContext ctx) { @@ -545,7 +545,7 @@ protected InterfaceTypeExtensionDefinition createInterfaceTypeExtensionDefinitio List implementz = getImplementz(implementsInterfacesContext); def.implementz(implementz); def.definitions(createFieldDefinitions(ctx.extensionFieldsDefinition())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected UnionTypeDefinition createUnionTypeDefinition(GraphqlParser.UnionTypeDefinitionContext ctx) { @@ -564,7 +564,7 @@ protected UnionTypeDefinition createUnionTypeDefinition(GraphqlParser.UnionTypeD } } def.memberTypes(members); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected UnionTypeExtensionDefinition createUnionTypeExtensionDefinition(GraphqlParser.UnionTypeExtensionDefinitionContext ctx) { @@ -581,7 +581,7 @@ protected UnionTypeExtensionDefinition createUnionTypeExtensionDefinition(Graphq } def.memberTypes(members); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected EnumTypeDefinition createEnumTypeDefinition(GraphqlParser.EnumTypeDefinitionContext ctx) { @@ -594,7 +594,7 @@ protected EnumTypeDefinition createEnumTypeDefinition(GraphqlParser.EnumTypeDefi def.enumValueDefinitions( map(ctx.enumValueDefinitions().enumValueDefinition(), this::createEnumValueDefinition)); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected EnumTypeExtensionDefinition createEnumTypeExtensionDefinition(GraphqlParser.EnumTypeExtensionDefinitionContext ctx) { @@ -606,7 +606,7 @@ protected EnumTypeExtensionDefinition createEnumTypeExtensionDefinition(GraphqlP def.enumValueDefinitions( map(ctx.extensionEnumValueDefinitions().enumValueDefinition(), this::createEnumValueDefinition)); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected EnumValueDefinition createEnumValueDefinition(GraphqlParser.EnumValueDefinitionContext ctx) { @@ -615,7 +615,7 @@ protected EnumValueDefinition createEnumValueDefinition(GraphqlParser.EnumValueD addCommonData(def, ctx); def.description(newDescription(ctx.description())); def.directives(createDirectives(ctx.directives())); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected InputObjectTypeDefinition createInputObjectTypeDefinition(GraphqlParser.InputObjectTypeDefinitionContext ctx) { @@ -627,7 +627,7 @@ protected InputObjectTypeDefinition createInputObjectTypeDefinition(GraphqlParse if (ctx.inputObjectValueDefinitions() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.inputObjectValueDefinitions().inputValueDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected InputObjectTypeExtensionDefinition createInputObjectTypeExtensionDefinition(GraphqlParser.InputObjectTypeExtensionDefinitionContext ctx) { @@ -638,7 +638,7 @@ protected InputObjectTypeExtensionDefinition createInputObjectTypeExtensionDefin if (ctx.extensionInputObjectValueDefinitions() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.extensionInputObjectValueDefinitions().inputValueDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected DirectiveDefinition createDirectiveDefinition(GraphqlParser.DirectiveDefinitionContext ctx) { @@ -659,41 +659,41 @@ protected DirectiveDefinition createDirectiveDefinition(GraphqlParser.DirectiveD if (ctx.argumentsDefinition() != null) { def.inputValueDefinitions(createInputValueDefinitions(ctx.argumentsDefinition().inputValueDefinition())); } - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected DirectiveLocation createDirectiveLocation(GraphqlParser.DirectiveLocationContext ctx) { DirectiveLocation.Builder def = DirectiveLocation.newDirectiveLocation(); def.name(ctx.name().getText()); addCommonData(def, ctx); - return addToMap(def.build(), ctx); + return captureRuleContext(def.build(), ctx); } protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { if (ctx.IntValue() != null) { IntValue.Builder intValue = IntValue.newIntValue().value(new BigInteger(ctx.IntValue().getText())); addCommonData(intValue, ctx); - return addToMap(intValue.build(), ctx); + return captureRuleContext(intValue.build(), ctx); } else if (ctx.FloatValue() != null) { FloatValue.Builder floatValue = FloatValue.newFloatValue().value(new BigDecimal(ctx.FloatValue().getText())); addCommonData(floatValue, ctx); - return addToMap(floatValue.build(), ctx); + return captureRuleContext(floatValue.build(), ctx); } else if (ctx.BooleanValue() != null) { BooleanValue.Builder booleanValue = BooleanValue.newBooleanValue().value(Boolean.parseBoolean(ctx.BooleanValue().getText())); addCommonData(booleanValue, ctx); - return addToMap(booleanValue.build(), ctx); + return captureRuleContext(booleanValue.build(), ctx); } else if (ctx.NullValue() != null) { NullValue.Builder nullValue = NullValue.newNullValue(); addCommonData(nullValue, ctx); - return addToMap(nullValue.build(), ctx); + return captureRuleContext(nullValue.build(), ctx); } else if (ctx.StringValue() != null) { StringValue.Builder stringValue = StringValue.newStringValue().value(quotedString(ctx.StringValue())); addCommonData(stringValue, ctx); - return addToMap(stringValue.build(), ctx); + return captureRuleContext(stringValue.build(), ctx); } else if (ctx.enumValue() != null) { EnumValue.Builder enumValue = EnumValue.newEnumValue().name(ctx.enumValue().getText()); addCommonData(enumValue, ctx); - return addToMap(enumValue.build(), ctx); + return captureRuleContext(enumValue.build(), ctx); } else if (ctx.arrayValueWithVariable() != null) { ArrayValue.Builder arrayValue = ArrayValue.newArrayValue(); addCommonData(arrayValue, ctx); @@ -701,7 +701,7 @@ protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { for (GraphqlParser.ValueWithVariableContext valueWithVariableContext : ctx.arrayValueWithVariable().valueWithVariable()) { values.add(createValue(valueWithVariableContext)); } - return addToMap(arrayValue.values(values).build(), ctx); + return captureRuleContext(arrayValue.values(values).build(), ctx); } else if (ctx.objectValueWithVariable() != null) { ObjectValue.Builder objectValue = ObjectValue.newObjectValue(); addCommonData(objectValue, ctx); @@ -715,11 +715,11 @@ protected Value createValue(GraphqlParser.ValueWithVariableContext ctx) { .build(); objectFields.add(objectField); } - return addToMap(objectValue.objectFields(objectFields).build(), ctx); + return captureRuleContext(objectValue.objectFields(objectFields).build(), ctx); } else if (ctx.variable() != null) { VariableReference.Builder variableReference = VariableReference.newVariableReference().name(ctx.variable().name().getText()); addCommonData(variableReference, ctx); - return addToMap(variableReference.build(), ctx); + return captureRuleContext(variableReference.build(), ctx); } return assertShouldNeverHappen(); } @@ -728,27 +728,27 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { if (ctx.IntValue() != null) { IntValue.Builder intValue = IntValue.newIntValue().value(new BigInteger(ctx.IntValue().getText())); addCommonData(intValue, ctx); - return addToMap(intValue.build(), ctx); + return captureRuleContext(intValue.build(), ctx); } else if (ctx.FloatValue() != null) { FloatValue.Builder floatValue = FloatValue.newFloatValue().value(new BigDecimal(ctx.FloatValue().getText())); addCommonData(floatValue, ctx); - return addToMap(floatValue.build(), ctx); + return captureRuleContext(floatValue.build(), ctx); } else if (ctx.BooleanValue() != null) { BooleanValue.Builder booleanValue = BooleanValue.newBooleanValue().value(Boolean.parseBoolean(ctx.BooleanValue().getText())); addCommonData(booleanValue, ctx); - return addToMap(booleanValue.build(), ctx); + return captureRuleContext(booleanValue.build(), ctx); } else if (ctx.NullValue() != null) { NullValue.Builder nullValue = NullValue.newNullValue(); addCommonData(nullValue, ctx); - return addToMap(nullValue.build(), ctx); + return captureRuleContext(nullValue.build(), ctx); } else if (ctx.StringValue() != null) { StringValue.Builder stringValue = StringValue.newStringValue().value(quotedString(ctx.StringValue())); addCommonData(stringValue, ctx); - return addToMap(stringValue.build(), ctx); + return captureRuleContext(stringValue.build(), ctx); } else if (ctx.enumValue() != null) { EnumValue.Builder enumValue = EnumValue.newEnumValue().name(ctx.enumValue().getText()); addCommonData(enumValue, ctx); - return addToMap(enumValue.build(), ctx); + return captureRuleContext(enumValue.build(), ctx); } else if (ctx.arrayValue() != null) { ArrayValue.Builder arrayValue = ArrayValue.newArrayValue(); addCommonData(arrayValue, ctx); @@ -756,7 +756,7 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { for (GraphqlParser.ValueContext valueContext : ctx.arrayValue().value()) { values.add(createValue(valueContext)); } - return addToMap(arrayValue.values(values).build(), ctx); + return captureRuleContext(arrayValue.values(values).build(), ctx); } else if (ctx.objectValue() != null) { ObjectValue.Builder objectValue = ObjectValue.newObjectValue(); addCommonData(objectValue, ctx); @@ -769,7 +769,7 @@ protected Value createValue(GraphqlParser.ValueContext ctx) { .build(); objectFields.add(objectField); } - return addToMap(objectValue.objectFields(objectFields).build(), ctx); + return captureRuleContext(objectValue.objectFields(objectFields).build(), ctx); } return assertShouldNeverHappen(); } @@ -928,7 +928,7 @@ private List getImplementz(GraphqlParser.ImplementsInterfacesContext imple return implementz; } - private > T addToMap(T node, ParserRuleContext ctx) { + private > T captureRuleContext(T node, ParserRuleContext ctx) { if (nodeToRuleMap != null) { nodeToRuleMap.put(node, ctx); } From 0012485e5fb937afd553e1e00c2990f331c57f97 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 7 Aug 2022 13:52:58 +1000 Subject: [PATCH 065/294] Remove deprecated @fetch directive --- .../idl/FetchSchemaDirectiveWiring.java | 45 ------------------- .../schema/idl/SchemaGeneratorTest.groovy | 33 -------------- .../schema/idl/WiringFactoryTest.groovy | 39 ---------------- 3 files changed, 117 deletions(-) delete mode 100644 src/main/java/graphql/schema/idl/FetchSchemaDirectiveWiring.java diff --git a/src/main/java/graphql/schema/idl/FetchSchemaDirectiveWiring.java b/src/main/java/graphql/schema/idl/FetchSchemaDirectiveWiring.java deleted file mode 100644 index 32898e3f35..0000000000 --- a/src/main/java/graphql/schema/idl/FetchSchemaDirectiveWiring.java +++ /dev/null @@ -1,45 +0,0 @@ -package graphql.schema.idl; - -import graphql.Internal; -import graphql.execution.ValuesResolver; -import graphql.schema.DataFetcher; -import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.PropertyDataFetcher; - -import java.util.List; -import java.util.Optional; - -import static graphql.DirectivesUtil.directiveWithArg; -import static graphql.schema.FieldCoordinates.coordinates; - -/** - * This adds ' @fetch(from : "otherName") ' support so you can rename what property is read for a given field - * - * @deprecated This support introduces a non standard directive and has interfere with some implementations. This is no longer - * installed default and will be removed in a future version - */ -@Internal -@Deprecated -public class FetchSchemaDirectiveWiring implements SchemaDirectiveWiring { - public static final String FETCH = "fetch"; - - @Override - public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment environment) { - GraphQLFieldDefinition field = environment.getElement(); - String fetchName = atFetchFromSupport(field.getName(), field.getDirectives()); - DataFetcher dataFetcher = new PropertyDataFetcher(fetchName); - - environment.getCodeRegistry().dataFetcher(coordinates(environment.getFieldsContainer(), field), dataFetcher); - return field; - } - - - private String atFetchFromSupport(String fieldName, List directives) { - // @fetch(from : "name") - Optional from = directiveWithArg(directives, FETCH, "from"); - return from.map(arg -> (String) ValuesResolver.valueToInternalValue(arg.getArgumentValue(), arg.getType())).orElse(fieldName); - } - -} diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 4d9f5f7029..a6e3afc89c 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -1786,39 +1786,6 @@ class SchemaGeneratorTest extends Specification { } - def "@fetch directive is respected if added"() { - def spec = """ - - directive @fetch(from : String!) on FIELD_DEFINITION - - type Query { - name : String, - homePlanet: String @fetch(from : "planetOfBirth") - } - """ - - def wiring = RuntimeWiring.newRuntimeWiring().directiveWiring(new FetchSchemaDirectiveWiring()).build() - def schema = schema(spec, wiring) - - GraphQLObjectType type = schema.getType("Query") as GraphQLObjectType - - expect: - def fetcher = schema.getCodeRegistry().getDataFetcher(type, type.getFieldDefinition("homePlanet")) - fetcher instanceof PropertyDataFetcher - - PropertyDataFetcher propertyDataFetcher = fetcher as PropertyDataFetcher - propertyDataFetcher.getPropertyName() == "planetOfBirth" - // - // no directive - plain name - // - def fetcher2 = schema.getCodeRegistry().getDataFetcher(type, type.getFieldDefinition("name")) - fetcher2 instanceof PropertyDataFetcher - - PropertyDataFetcher propertyDataFetcher2 = fetcher2 as PropertyDataFetcher - propertyDataFetcher2.getPropertyName() == "name" - } - - def "does not break for circular references to interfaces"() { def spec = """ interface MyInterface { diff --git a/src/test/groovy/graphql/schema/idl/WiringFactoryTest.groovy b/src/test/groovy/graphql/schema/idl/WiringFactoryTest.groovy index fab6cde1dc..edb7efc12d 100644 --- a/src/test/groovy/graphql/schema/idl/WiringFactoryTest.groovy +++ b/src/test/groovy/graphql/schema/idl/WiringFactoryTest.groovy @@ -305,45 +305,6 @@ class WiringFactoryTest extends Specification { wiringFactory.fields == ["id", "homePlanet"] } - def "@fetch directive is respected by default data fetcher wiring if added"() { - def spec = """ - - directive @fetch(from : String!) on FIELD_DEFINITION - - type Query { - name : String, - homePlanet: String @fetch(from : "planetOfBirth") - } - """ - - def wiringFactory = new WiringFactory() { - } - def wiring = RuntimeWiring.newRuntimeWiring() - .wiringFactory(wiringFactory) - .directiveWiring(new FetchSchemaDirectiveWiring()) - .build() - - def schema = TestUtil.schema(spec, wiring) - - GraphQLObjectType type = schema.getType("Query") as GraphQLObjectType - - expect: - def fetcher = schema.getCodeRegistry().getDataFetcher(type, type.getFieldDefinition("homePlanet")) - fetcher instanceof PropertyDataFetcher - - PropertyDataFetcher propertyDataFetcher = fetcher as PropertyDataFetcher - propertyDataFetcher.getPropertyName() == "planetOfBirth" - // - // no directive - plain name - // - def fetcher2 = schema.getCodeRegistry().getDataFetcher(type, type.getFieldDefinition("name")) - fetcher2 instanceof PropertyDataFetcher - - PropertyDataFetcher propertyDataFetcher2 = fetcher2 as PropertyDataFetcher - propertyDataFetcher2.getPropertyName() == "name" - - } - def "Name"() { WiringFactory wf = new WiringFactory() { @Override From 84611561ffaf0a859016219de5814436cfd9b964 Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Tue, 9 Aug 2022 16:41:51 -0500 Subject: [PATCH 066/294] Added string to array function --- .github/workflows/create_job.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_job.js b/.github/workflows/create_job.js index 5d9a7bdeae..444c816a0d 100644 --- a/.github/workflows/create_job.js +++ b/.github/workflows/create_job.js @@ -12,12 +12,18 @@ const constructPayload = () => { const payloadStructure = { "jobId": jobId, "commitHash": commitHash, - "classes": classes, + "classes": castClassesStringToArray(classes), "pullRequest": pullRequestNumber, } return payloadStructure; } +const castClassesStringToArray = (classes) => { + const classesArray = classes.split(','); + const timedClassesArray = classesArray.map(currentClass => currentClass.trim()); + return timedClassesArray; +} + const formatPayload = (payloadStructure) => { const parsedPayload = JSON.stringify(JSON.stringify(payloadStructure)); const payload = `{"argument": ${parsedPayload}}`; From e7ff81517afe6d752458e32aa143b0beb5f1f8c4 Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Tue, 9 Aug 2022 16:54:56 -0500 Subject: [PATCH 067/294] Fixed typos on string to array function --- .github/workflows/create_job.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create_job.js b/.github/workflows/create_job.js index 444c816a0d..7112ad32ef 100644 --- a/.github/workflows/create_job.js +++ b/.github/workflows/create_job.js @@ -12,16 +12,16 @@ const constructPayload = () => { const payloadStructure = { "jobId": jobId, "commitHash": commitHash, - "classes": castClassesStringToArray(classes), + "classes": convertClassesStringToArray(classes), "pullRequest": pullRequestNumber, } return payloadStructure; } -const castClassesStringToArray = (classes) => { +const convertClassesStringToArray = (classes) => { const classesArray = classes.split(','); - const timedClassesArray = classesArray.map(currentClass => currentClass.trim()); - return timedClassesArray; + const trimmedClassesArray = classesArray.map(currentClass => currentClass.trim()); + return trimmedClassesArray; } const formatPayload = (payloadStructure) => { From 923a034e82e64230e387dea4388ca4feb46f02c5 Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Thu, 11 Aug 2022 08:34:52 +1000 Subject: [PATCH 068/294] Rename to PrettyAstPrinter; improve tests; add javadoc --- ...ettyPrinter.java => PrettyAstPrinter.java} | 8 +++---- .../parser/GraphqlAntlrToLanguage.java | 24 ++++++++++++++++++- ...est.groovy => PrettyAstPrinterTest.groovy} | 18 ++++---------- 3 files changed, 32 insertions(+), 18 deletions(-) rename src/main/java/graphql/language/{PrettyPrinter.java => PrettyAstPrinter.java} (97%) rename src/test/groovy/graphql/language/{PrettyPrinterTest.groovy => PrettyAstPrinterTest.groovy} (93%) diff --git a/src/main/java/graphql/language/PrettyPrinter.java b/src/main/java/graphql/language/PrettyAstPrinter.java similarity index 97% rename from src/main/java/graphql/language/PrettyPrinter.java rename to src/main/java/graphql/language/PrettyAstPrinter.java index c8a680f164..18be3e999f 100644 --- a/src/main/java/graphql/language/PrettyPrinter.java +++ b/src/main/java/graphql/language/PrettyAstPrinter.java @@ -23,15 +23,15 @@ * @see AstPrinter */ @PublicApi -public class PrettyPrinter extends AstPrinter { +public class PrettyAstPrinter extends AstPrinter { private final CommentParser commentParser; private final PrettyPrinterOptions options; - public PrettyPrinter(NodeToRuleCapturingParser.ParserContext parserContext) { + public PrettyAstPrinter(NodeToRuleCapturingParser.ParserContext parserContext) { this(parserContext, PrettyPrinterOptions.defaultOptions); } - public PrettyPrinter(NodeToRuleCapturingParser.ParserContext parserContext, PrettyPrinterOptions options) { + public PrettyAstPrinter(NodeToRuleCapturingParser.ParserContext parserContext, PrettyPrinterOptions options) { super(false); this.commentParser = new CommentParser(parserContext); this.options = options; @@ -68,7 +68,7 @@ public static String print(String schemaDefinition, PrettyPrinterOptions options NodeToRuleCapturingParser parser = new NodeToRuleCapturingParser(); Document document = parser.parseDocument(schemaDefinition); - return new PrettyPrinter(parser.getParserContext(), options).print(document); + return new PrettyAstPrinter(parser.getParserContext(), options).print(document); } private NodePrinter document() { diff --git a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java index 012da493f2..5539234806 100644 --- a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java +++ b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java @@ -94,15 +94,37 @@ public class GraphqlAntlrToLanguage { private final Map, ParserRuleContext> nodeToRuleMap; + /** + * @param tokens the token stream + * @param multiSourceReader the source of the query document + */ public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { this(tokens, multiSourceReader, null); } + /** + * @param tokens the token stream + * @param multiSourceReader the source of the query document + * @param parserOptions the parser options + */ public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { this(tokens, multiSourceReader, parserOptions, null); } - public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions, @Nullable Map, ParserRuleContext> nodeToRuleMap) { + /** + * @param tokens the token stream + * @param multiSourceReader the source of the query document + * @param parserOptions the parser options + * @param nodeToRuleMap a map that will be used to accumulate the ParserRuleContext associated with each node. + * This information can be used after the parsing process is done to access some elements + * that are usually lost during parsing. If the map is "null", no accumulation will be performed. + */ + public GraphqlAntlrToLanguage( + CommonTokenStream tokens, + MultiSourceReader multiSourceReader, + ParserOptions parserOptions, + @Nullable Map, ParserRuleContext> nodeToRuleMap + ) { this.tokens = tokens; this.multiSourceReader = multiSourceReader; this.parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultParserOptions()); diff --git a/src/test/groovy/graphql/language/PrettyPrinterTest.groovy b/src/test/groovy/graphql/language/PrettyAstPrinterTest.groovy similarity index 93% rename from src/test/groovy/graphql/language/PrettyPrinterTest.groovy rename to src/test/groovy/graphql/language/PrettyAstPrinterTest.groovy index 83922d0769..0fab95cbf6 100644 --- a/src/test/groovy/graphql/language/PrettyPrinterTest.groovy +++ b/src/test/groovy/graphql/language/PrettyAstPrinterTest.groovy @@ -1,9 +1,8 @@ package graphql.language -import graphql.parser.NodeToRuleCapturingParser import spock.lang.Specification -class PrettyPrinterTest extends Specification { +class PrettyAstPrinterTest extends Specification { def "can print type with comments"() { given: @@ -504,26 +503,19 @@ a: A, b: B): Type } ''' when: - def options = PrettyPrinter.PrettyPrinterOptions + def options = PrettyAstPrinter.PrettyPrinterOptions .builder() - .indentType(PrettyPrinter.PrettyPrinterOptions.IndentType.TAB) + .indentType(PrettyAstPrinter.PrettyPrinterOptions.IndentType.TAB) .indentWith(1) .build() - def parser = new NodeToRuleCapturingParser() - def document = parser.parseDocument(input) - - def result = new PrettyPrinter(parser.parserContext, options).print(document) + def result = PrettyAstPrinter.print(input, options) then: result == expected } private static String print(String input) { - def parser = new NodeToRuleCapturingParser() - def document = parser.parseDocument(input) - def parserContext = parser.parserContext - - return new PrettyPrinter(parserContext).print(document) + return PrettyAstPrinter.print(input, PrettyAstPrinter.PrettyPrinterOptions.defaultOptions()) } } From 327559f51a16d8fc4a52dd97805c020b08fd098d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 14 Aug 2022 20:05:57 +1000 Subject: [PATCH 069/294] Add German validation errors --- .../resources/i18n/Validation_de.properties | 99 +++++++++++++++++++ src/test/groovy/graphql/i18n/I18nTest.groovy | 8 ++ 2 files changed, 107 insertions(+) create mode 100644 src/main/resources/i18n/Validation_de.properties diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties new file mode 100644 index 0000000000..7c3ece0b60 --- /dev/null +++ b/src/main/resources/i18n/Validation_de.properties @@ -0,0 +1,99 @@ +# +# This resource bundle is used for the query validation code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +# Prior to Java 9, properties files are encoded in ISO-8859-1. +# Leider, we have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe +ExecutableDefinitions.notExecutableType=Validierungsfehler ({0}) : Die Typdefinition ''{1}'' ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableSchema=Validierungsfehler ({0}) : Die Schemadefinition ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableDirective=Validierungsfehler ({0}) : Die Direktivendefinition ''{1}'' ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableDefinition=Validierungsfehler ({0}) : Die angegebene Definition ist nicht ausf\u00fchrbar +# +FieldsOnCorrectType.unknownField=Validierungsfehler ({0}) : Feld ''{1}'' vom Typ ''{2}'' ist nicht definiert +# +FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Die Bedingung f\u00fcr den Inline-Fragmenttyp ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen +FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Die Bedingung des Fragmenttyps ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen +# +KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes Direktivenargument ''{1}'' +KnownArgumentNames.unknownFieldArg=Validierungsfehler ({0}) : Unbekanntes Feldargument ''{1}'' +# +KnownDirectives.unknownDirective=Validierungsfehler ({0}) : Unbekannte Direktive ''{1}'' +KnownDirectives.directiveNotAllowed=Validierungsfehler ({0}) : Direktive ''{1}'' ist hier nicht erlaubt +# +KnownFragmentNames.undefinedFragment=Validierungsfehler ({0}) : Undefiniertes Fragment ''{1}'' +# +KnownTypeNames.unknownType=Validierungsfehler ({0}) : Unbekannter Typ ''{1}'' +# +LoneAnonymousOperation.withOthers=Validierungsfehler ({0}) : Anonyme Operation mit anderen Operationen +LoneAnonymousOperation.namedOperation=Validierungsfehler ({0}) : Operation ''{1}'' folgt der anonymen Operation +# +NoFragmentCycles.cyclesNotAllowed=Validierungsfehler ({0}) : Fragmentzyklen nicht erlaubt +# +NoUndefinedVariables.undefinedVariable=Validierungsfehler ({0}) : Undefinierte Variable ''{1}'' +# +NoUnusedFragments.unusedFragments=Validierungsfehler ({0}) : Unbenutztes Fragment ''{1}'' +# +NoUnusedVariables.unusedVariable=Validierungsfehler ({0}) : Unbenutzte Variable ''{1}'' +# +OverlappingFieldsCanBeMerged.differentFields=Validierungsfehler ({0}) : ''{1}'' : ''{2}'' und ''{3}'' sind unterschiedliche Felder +OverlappingFieldsCanBeMerged.differentArgs=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Argumente +OverlappingFieldsCanBeMerged.differentNullability=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche NULL-Zul\u00e4ssigkeitsformen +OverlappingFieldsCanBeMerged.differentLists=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Listenformen +OverlappingFieldsCanBeMerged.differentReturnTypes=Validierungsfehler ({0}) : ''{1}'' : gibt verschiedene Typen ''{2}'' und ''{3}'' zur\u00fcck +# +PossibleFragmentSpreads.inlineIncompatibleTypes=Validierungsfehler ({0}) : Fragment kann hier nicht verbreitet werden, da Objekte vom Typ ''{1}'' niemals vom Typ ''{2}'' sein k\u00f6nnen +PossibleFragmentSpreads.fragmentIncompatibleTypes=Validierungsfehler ({0}) : Fragment ''{1}'' kann hier nicht verbreitet werden, da Objekte vom Typ ''{2}'' niemals vom Typ ''{3}'' sein k\u00f6nnen +# +ProvidedNonNullArguments.missingFieldArg=Validierungsfehler ({0}) : Fehlendes Feldargument ''{1}'' +ProvidedNonNullArguments.missingDirectiveArg=Validierungsfehler ({0}) : Fehlendes Direktivenargument ''{1}'' +ProvidedNonNullArguments.nullValue=Validierungsfehler ({0}) : Nullwert f\u00fcr Nicht-Null-Feldargument ''{1}'' +# +ScalarLeaves.subselectionOnLeaf=Validierungsfehler ({0}) : Unterauswahl f\u00fcr Blatttyp ''{1}'' von Feld ''{2}'' nicht zul\u00e4ssig +ScalarLeaves.subselectionRequired=Validierungsfehler ({0}) : Unterauswahl erforderlich f\u00fcr Typ ''{1}'' des Feldes ''{2}'' +# +SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld haben +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld mit Fragmenten haben +SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Stammfeld ''{2}'' kann kein Selbstpr\u00fcfungsfeld sein +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Fragmentstammfeld ''{2}'' kann kein Selbstpr\u00fcfungsfeld sein +# +UniqueArgumentNames.uniqueArgument=Validierungsfehler ({0}) : Es kann nur ein Argument namens ''{1}'' geben +# +UniqueDirectiveNamesPerLocation.uniqueDirectives=Validierungsfehler ({0}) : Nicht wiederholbare Direktive m\u00fcssen innerhalb einer Lokation eindeutig benannt werden. Die Direktive ''{1}'', die auf einem ''{2}'' verwendet wird, ist nicht eindeutig +# +UniqueFragmentNames.oneFragment=Validierungsfehler ({0}) : Es kann nur ein Fragment namens ''{1}'' geben +# +UniqueOperationNames.oneOperation=Validierungsfehler ({0}) : Es kann nur eine Operation namens ''{1}'' geben +# +UniqueVariableNames.oneVariable=Validierungsfehler ({0}) : Es kann nur eine Variable namens ''{1}'' geben +# +VariableDefaultValuesOfCorrectType.badDefault=Validierungsfehler ({0}) : Ung\u00fcltiger Standardwert ''{1}'' f\u00fcr Typ ''{2}'' +# +VariablesAreInputTypes.wrongType=Validierungsfehler ({0}) : Eingabevariable ''{1}'' Typ ''{2}'' ist kein Eingabetyp +# +VariableTypesMatchRule.unexpectedType=Validierungsfehler ({0}) : Der Variablentyp ''{1}'' stimmt nicht mit dem erwarteten Typ ''{2}'' \u00fcberein +# +# These are used but IDEA cant find them easily as being called +# +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleNullError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' darf nicht null sein +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleScalarError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' +ArgumentValidationUtil.handleScalarErrorCustomMessage=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' - {4} +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleEnumError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' +ArgumentValidationUtil.handleEnumErrorCustomMessage=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' - {4} +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleNotObjectError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' muss ein Objekttyp sein +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleMissingFieldsError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' fehlen Pflichtfelder ''{3}'' +# suppress inspection "UnusedProperty" +ArgumentValidationUtil.handleExtraFieldError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' enth\u00e4lt ein Feld nicht in ''{3}'': ''{4}'' +# diff --git a/src/test/groovy/graphql/i18n/I18nTest.groovy b/src/test/groovy/graphql/i18n/I18nTest.groovy index 7abe1346a6..ceac9e88ea 100644 --- a/src/test/groovy/graphql/i18n/I18nTest.groovy +++ b/src/test/groovy/graphql/i18n/I18nTest.groovy @@ -26,6 +26,14 @@ class I18nTest extends Specification { } } + def "A non-default bundle can be read"() { + def i18n = I18n.i18n(BundleType.Validation, Locale.GERMAN) + when: + def message = i18n.msg("ExecutableDefinitions.notExecutableType") + then: + message == "Validierungsfehler ({0}) : Die Typdefinition '{1}' ist nicht ausführbar" + } + static def assertBundleStaticShape(ResourceBundle bundle) { def enumeration = bundle.getKeys() while (enumeration.hasMoreElements()) { From 09fdf2241baede32e3fa6e780c18c126c3fa564f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 14 Aug 2022 20:07:45 +1000 Subject: [PATCH 070/294] Tidy up IDE error --- src/main/resources/i18n/Validation.properties | 2 ++ src/main/resources/i18n/Validation_de.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/resources/i18n/Validation.properties b/src/main/resources/i18n/Validation.properties index c6eb1ae49b..3b2dfb731f 100644 --- a/src/main/resources/i18n/Validation.properties +++ b/src/main/resources/i18n/Validation.properties @@ -84,9 +84,11 @@ VariableTypesMatchRule.unexpectedType=Validation error ({0}) : Variable type ''{ ArgumentValidationUtil.handleNullError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' must not be null # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleScalarError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' +# suppress inspection "UnusedProperty" ArgumentValidationUtil.handleScalarErrorCustomMessage=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' - {4} # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleEnumError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' +# suppress inspection "UnusedProperty" ArgumentValidationUtil.handleEnumErrorCustomMessage=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is not a valid ''{3}'' - {4} # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleNotObjectError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' must be an object type diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 7c3ece0b60..e4eb7d5c0b 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -86,9 +86,11 @@ VariableTypesMatchRule.unexpectedType=Validierungsfehler ({0}) : Der Variablenty ArgumentValidationUtil.handleNullError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' darf nicht null sein # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleScalarError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' +# suppress inspection "UnusedProperty" ArgumentValidationUtil.handleScalarErrorCustomMessage=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' - {4} # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleEnumError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' +# suppress inspection "UnusedProperty" ArgumentValidationUtil.handleEnumErrorCustomMessage=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' - {4} # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleNotObjectError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' muss ein Objekttyp sein From e5f8367dff3cdb31796fe4042df0eb9df0e81881 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 14 Aug 2022 20:31:57 +1000 Subject: [PATCH 071/294] Tidy up --- src/main/resources/i18n/Validation_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index e4eb7d5c0b..7f50201de1 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -22,7 +22,7 @@ FieldsOnCorrectType.unknownField=Validierungsfehler ({0}) : Feld ''{1}'' vom Typ FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Die Bedingung f\u00fcr den Inline-Fragmenttyp ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Die Bedingung des Fragmenttyps ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen # -KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes Direktivenargument ''{1}'' +KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes Direktivenargument ''{1}'' KnownArgumentNames.unknownFieldArg=Validierungsfehler ({0}) : Unbekanntes Feldargument ''{1}'' # KnownDirectives.unknownDirective=Validierungsfehler ({0}) : Unbekannte Direktive ''{1}'' From 7abed8c585802e82ab162052f63389be4bdef0f2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 14 Aug 2022 21:31:41 +1000 Subject: [PATCH 072/294] Fix word --- src/main/resources/i18n/Validation_de.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 7f50201de1..39e72a7906 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -19,8 +19,8 @@ ExecutableDefinitions.notExecutableDefinition=Validierungsfehler ({0}) : Die ang # FieldsOnCorrectType.unknownField=Validierungsfehler ({0}) : Feld ''{1}'' vom Typ ''{2}'' ist nicht definiert # -FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Die Bedingung f\u00fcr den Inline-Fragmenttyp ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen -FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Die Bedingung des Fragmenttyps ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Vereinigung stehen +FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Die Bedingung f\u00fcr den Inline-Fragmenttyp ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Union stehen +FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Die Bedingung des Fragmenttyps ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Union stehen # KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes Direktivenargument ''{1}'' KnownArgumentNames.unknownFieldArg=Validierungsfehler ({0}) : Unbekanntes Feldargument ''{1}'' From 3d8c552deadff45242f79ab9b48e13804d7b96bd Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 14 Aug 2022 22:18:23 +1000 Subject: [PATCH 073/294] Better words --- src/main/resources/i18n/Validation_de.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 39e72a7906..591dd5d4e7 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -45,7 +45,7 @@ NoUnusedVariables.unusedVariable=Validierungsfehler ({0}) : Unbenutzte Variable # OverlappingFieldsCanBeMerged.differentFields=Validierungsfehler ({0}) : ''{1}'' : ''{2}'' und ''{3}'' sind unterschiedliche Felder OverlappingFieldsCanBeMerged.differentArgs=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Argumente -OverlappingFieldsCanBeMerged.differentNullability=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche NULL-Zul\u00e4ssigkeitsformen +OverlappingFieldsCanBeMerged.differentNullability=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Nullbarkeitsformen OverlappingFieldsCanBeMerged.differentLists=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Listenformen OverlappingFieldsCanBeMerged.differentReturnTypes=Validierungsfehler ({0}) : ''{1}'' : gibt verschiedene Typen ''{2}'' und ''{3}'' zur\u00fcck # @@ -61,8 +61,8 @@ ScalarLeaves.subselectionRequired=Validierungsfehler ({0}) : Unterauswahl erford # SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld haben SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld mit Fragmenten haben -SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Stammfeld ''{2}'' kann kein Selbstpr\u00fcfungsfeld sein -SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Fragmentstammfeld ''{2}'' kann kein Selbstpr\u00fcfungsfeld sein +SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Stammfeld ''{2}'' kann kein Introspektionsfeld sein +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Fragmentstammfeld ''{2}'' kann kein Introspektionsfeld sein # UniqueArgumentNames.uniqueArgument=Validierungsfehler ({0}) : Es kann nur ein Argument namens ''{1}'' geben # From 36c76c5c7514b8fff9c42ae04cde979521c48af1 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 15 Aug 2022 03:10:01 -0400 Subject: [PATCH 074/294] docs: update latest release badge to 19 (#2918) --- README.md | 8 ++------ README.zh_cn.md | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a0a65e3b81..e58d92d2f9 100644 --- a/README.md +++ b/README.md @@ -5,25 +5,23 @@ Discuss and ask questions in our Discussions: https://github.com/graphql-java/gr This is a [GraphQL](https://github.com/graphql/graphql-spec) Java implementation. [![Build](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml/badge.svg)](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml) -[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=18)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) +[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=19)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md) - ### Documentation We have a tutorial for beginners: [Getting started with GraphQL Java and Spring Boot](https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/) For details how to use `graphql-java` please look at the documentation: https://www.graphql-java.com/documentation/getting-started - Please take a look at our [list of releases](https://github.com/graphql-java/graphql-java/releases) if you want to learn more about new releases and the changelog. ### Code of Conduct Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this project (commenting or opening PR/Issues etc) you are agreeing to follow this conduct, so please -take the time to read it. +take the time to read it. ### License @@ -34,5 +32,3 @@ Copyright (c) 2015, Andreas Marek and [Contributors](https://github.com/graphql- ![YourKit](https://www.yourkit.com/images/yklogo.png) [YourKit](https://www.yourkit.com/) supports this project by providing the YourKit Java Profiler. - - diff --git a/README.zh_cn.md b/README.zh_cn.md index b85937e76f..277d449449 100644 --- a/README.zh_cn.md +++ b/README.zh_cn.md @@ -5,11 +5,10 @@ 该组件是 [GraphQL 规范](https://github.com/graphql/graphql-spec) 的 Java 实现。 [![Build](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml/badge.svg)](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml) -[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=18)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) +[![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=19)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md) - ### 文档 入门教程:[Getting started with GraphQL Java and Spring Boot](https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/) From 086aa1b9dc660ab6663abc748219eb1811b981fb Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Mon, 15 Aug 2022 12:28:39 +0200 Subject: [PATCH 075/294] Fix printing directives when they contain something like a formatting specifier (#2919) (#2920) --- .../graphql/schema/idl/SchemaPrinter.java | 2 +- .../schema/idl/SchemaPrinterTest.groovy | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/idl/SchemaPrinter.java b/src/main/java/graphql/schema/idl/SchemaPrinter.java index ea0df6b2e4..3683a0ddab 100644 --- a/src/main/java/graphql/schema/idl/SchemaPrinter.java +++ b/src/main/java/graphql/schema/idl/SchemaPrinter.java @@ -672,7 +672,7 @@ private SchemaElementPrinter inputObjectPrinter() { String astValue = printAst(defaultValue, fd.getType()); out.format(" = %s", astValue); } - out.format(directivesString(GraphQLInputObjectField.class, fd.isDeprecated(), fd)); + out.print(directivesString(GraphQLInputObjectField.class, fd.isDeprecated(), fd)); out.format("\n"); }); out.format("}"); diff --git a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy index 5f7f6ce409..e763932de6 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy @@ -2012,6 +2012,28 @@ type Query { ''' } + def "directive containing formatting specifiers"() { + def constraintDirective = GraphQLDirective.newDirective().name("constraint") + .argument({ + it.name("regex").type(GraphQLString).valueProgrammatic("%") + }) + .build() + GraphQLInputObjectType type = GraphQLInputObjectType.newInputObject().name("Person") + .field({ it.name("thisMustBeAPercentageSign").type(GraphQLString).withDirective(constraintDirective) }) + .build() + + when: + + def result = new SchemaPrinter().print(type) + + + then: + result == '''input Person { + thisMustBeAPercentageSign: String @constraint(regex : "%") +} +''' + } + def "can specify a new ordering for the schema printer"() { def sdl = """ From 6ab2c9db40f5b339dcfa65b1fe35303ae9fab6c0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 16 Aug 2022 09:57:10 +1000 Subject: [PATCH 076/294] restrict access more and declare it experimental --- src/main/java/graphql/language/AstPrinter.java | 10 +++++----- src/main/java/graphql/language/PrettyAstPrinter.java | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/language/AstPrinter.java b/src/main/java/graphql/language/AstPrinter.java index f6bd7aacc9..218dabd59e 100644 --- a/src/main/java/graphql/language/AstPrinter.java +++ b/src/main/java/graphql/language/AstPrinter.java @@ -25,7 +25,7 @@ public class AstPrinter { private final boolean compactMode; - protected AstPrinter(boolean compactMode) { + AstPrinter(boolean compactMode) { this.compactMode = compactMode; printers.put(Argument.class, argument()); printers.put(ArrayValue.class, value()); @@ -463,11 +463,11 @@ private String node(Node node, Class startClass) { } @SuppressWarnings("unchecked") - protected NodePrinter _findPrinter(Node node) { + NodePrinter _findPrinter(Node node) { return _findPrinter(node, null); } - protected NodePrinter _findPrinter(Node node, Class startClass) { + NodePrinter _findPrinter(Node node, Class startClass) { if (node == null) { return (out, type) -> { }; @@ -712,7 +712,7 @@ private static void printImpl(StringBuilder writer, Node node, boolean compactMo * * @param the type of node */ - protected interface NodePrinter { + interface NodePrinter { void print(StringBuilder out, T node); } @@ -721,7 +721,7 @@ protected interface NodePrinter { * @param nodeClass the class of the {@link Node} * @param nodePrinter the custom {@link NodePrinter} */ - protected void replacePrinter(Class nodeClass, NodePrinter nodePrinter) { + void replacePrinter(Class nodeClass, NodePrinter nodePrinter) { this.printers.put(nodeClass, nodePrinter); } } diff --git a/src/main/java/graphql/language/PrettyAstPrinter.java b/src/main/java/graphql/language/PrettyAstPrinter.java index 18be3e999f..2ff364ec77 100644 --- a/src/main/java/graphql/language/PrettyAstPrinter.java +++ b/src/main/java/graphql/language/PrettyAstPrinter.java @@ -1,5 +1,6 @@ package graphql.language; +import graphql.ExperimentalApi; import graphql.PublicApi; import graphql.collect.ImmutableKit; import graphql.parser.CommentParser; @@ -22,7 +23,7 @@ * * @see AstPrinter */ -@PublicApi +@ExperimentalApi public class PrettyAstPrinter extends AstPrinter { private final CommentParser commentParser; private final PrettyPrinterOptions options; From 202d443f49bcaa0bfad6278fc62563d15fe94dd1 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 16 Aug 2022 18:58:02 +1000 Subject: [PATCH 077/294] Delete nextgen engine and instrumentation --- .../nextgen/Instrumentation.java | 52 -- .../InstrumentationCreateStateParameters.java | 27 - .../InstrumentationExecutionParameters.java | 90 --- .../InstrumentationValidationParameters.java | 38 -- .../instrumentation/nextgen/package-info.java | 4 - .../execution/nextgen/BatchedDataFetcher.java | 12 - .../nextgen/BatchedExecutionStrategy.java | 161 ----- .../graphql/execution/nextgen/Common.java | 41 -- .../nextgen/DefaultExecutionStrategy.java | 80 --- .../graphql/execution/nextgen/Execution.java | 48 -- .../execution/nextgen/ExecutionHelper.java | 98 ---- .../execution/nextgen/ExecutionStrategy.java | 18 - .../nextgen/ExecutionStrategyUtil.java | 102 ---- .../nextgen/FetchedValueAnalysis.java | 203 ------- .../nextgen/FetchedValueAnalyzer.java | 214 ------- .../execution/nextgen/FieldSubSelection.java | 100 ---- .../execution/nextgen/ResultNodesCreator.java | 70 --- .../execution/nextgen/ValueFetcher.java | 230 -------- .../execution/nextgen/package-info.java | 11 - .../nextgen/result/ExecutionResultNode.java | 115 ---- .../result/LeafExecutionResultNode.java | 58 -- .../result/ListExecutionResultNode.java | 51 -- .../nextgen/result/NamedResultNode.java | 30 - .../result/ObjectExecutionResultNode.java | 53 -- .../nextgen/result/ResolvedValue.java | 101 ---- .../nextgen/result/ResultNodeAdapter.java | 49 -- .../nextgen/result/ResultNodeTraverser.java | 34 -- .../nextgen/result/ResultNodesUtil.java | 187 ------ .../result/RootExecutionResultNode.java | 58 -- .../result/UnresolvedObjectResultNode.java | 18 - src/main/java/graphql/nextgen/GraphQL.java | 380 ------------ .../java/graphql/nextgen/package-info.java | 10 - .../BatchedExecutionStrategyTest.groovy | 396 ------------- .../DefaultExecutionStrategyTest.groovy | 549 ------------------ .../result/ExecutionResultNodeTest.groovy | 90 --- .../ExecutionResultNodeTestUtils.groovy | 19 - .../result/ResultNodeAdapterTest.groovy | 36 -- .../nextgen/result/ResultNodesUtilTest.groovy | 33 -- .../graphql/nextgen/GraphqlNextGenTest.groovy | 32 - 39 files changed, 3898 deletions(-) delete mode 100644 src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java delete mode 100644 src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java delete mode 100644 src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java delete mode 100644 src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java delete mode 100644 src/main/java/graphql/execution/instrumentation/nextgen/package-info.java delete mode 100644 src/main/java/graphql/execution/nextgen/BatchedDataFetcher.java delete mode 100644 src/main/java/graphql/execution/nextgen/BatchedExecutionStrategy.java delete mode 100644 src/main/java/graphql/execution/nextgen/Common.java delete mode 100644 src/main/java/graphql/execution/nextgen/DefaultExecutionStrategy.java delete mode 100644 src/main/java/graphql/execution/nextgen/Execution.java delete mode 100644 src/main/java/graphql/execution/nextgen/ExecutionHelper.java delete mode 100644 src/main/java/graphql/execution/nextgen/ExecutionStrategy.java delete mode 100644 src/main/java/graphql/execution/nextgen/ExecutionStrategyUtil.java delete mode 100644 src/main/java/graphql/execution/nextgen/FetchedValueAnalysis.java delete mode 100644 src/main/java/graphql/execution/nextgen/FetchedValueAnalyzer.java delete mode 100644 src/main/java/graphql/execution/nextgen/FieldSubSelection.java delete mode 100644 src/main/java/graphql/execution/nextgen/ResultNodesCreator.java delete mode 100644 src/main/java/graphql/execution/nextgen/ValueFetcher.java delete mode 100644 src/main/java/graphql/execution/nextgen/package-info.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ExecutionResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/LeafExecutionResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ListExecutionResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/NamedResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ObjectExecutionResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ResolvedValue.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ResultNodeAdapter.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ResultNodeTraverser.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/ResultNodesUtil.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/RootExecutionResultNode.java delete mode 100644 src/main/java/graphql/execution/nextgen/result/UnresolvedObjectResultNode.java delete mode 100644 src/main/java/graphql/nextgen/GraphQL.java delete mode 100644 src/main/java/graphql/nextgen/package-info.java delete mode 100644 src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy delete mode 100644 src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy delete mode 100644 src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTest.groovy delete mode 100644 src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTestUtils.groovy delete mode 100644 src/test/groovy/graphql/execution/nextgen/result/ResultNodeAdapterTest.groovy delete mode 100644 src/test/groovy/graphql/execution/nextgen/result/ResultNodesUtilTest.groovy delete mode 100644 src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java deleted file mode 100644 index 3e59616284..0000000000 --- a/src/main/java/graphql/execution/instrumentation/nextgen/Instrumentation.java +++ /dev/null @@ -1,52 +0,0 @@ -package graphql.execution.instrumentation.nextgen; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.Internal; -import graphql.execution.instrumentation.DocumentAndVariables; -import graphql.execution.instrumentation.InstrumentationContext; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.language.Document; -import graphql.schema.GraphQLSchema; -import graphql.validation.ValidationError; - -import java.util.List; - -import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; - -@Internal -public interface Instrumentation { - - default InstrumentationState createState(InstrumentationCreateStateParameters parameters) { - return new InstrumentationState() { - }; - } - - default ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { - return executionInput; - } - - default DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters) { - return documentAndVariables; - } - - default GraphQLSchema instrumentSchema(GraphQLSchema graphQLSchema, InstrumentationExecutionParameters parameters) { - return graphQLSchema; - } - - default ExecutionResult instrumentExecutionResult(ExecutionResult result, InstrumentationExecutionParameters parameters) { - return result; - } - - default InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { - return noOp(); - } - - default InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { - return noOp(); - } - - default InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - return noOp(); - } -} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java deleted file mode 100644 index ff4ad52cdc..0000000000 --- a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationCreateStateParameters.java +++ /dev/null @@ -1,27 +0,0 @@ -package graphql.execution.instrumentation.nextgen; - -import graphql.ExecutionInput; -import graphql.Internal; -import graphql.schema.GraphQLSchema; - -/** - * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods - */ -@Internal -public class InstrumentationCreateStateParameters { - private final GraphQLSchema schema; - private final ExecutionInput executionInput; - - public InstrumentationCreateStateParameters(GraphQLSchema schema, ExecutionInput executionInput) { - this.schema = schema; - this.executionInput = executionInput; - } - - public GraphQLSchema getSchema() { - return schema; - } - - public ExecutionInput getExecutionInput() { - return executionInput; - } -} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java deleted file mode 100644 index 4217f09146..0000000000 --- a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationExecutionParameters.java +++ /dev/null @@ -1,90 +0,0 @@ -package graphql.execution.instrumentation.nextgen; - -import graphql.ExecutionInput; -import graphql.GraphQLContext; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.schema.GraphQLSchema; - -import java.util.Map; - -/** - * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods - */ -@Internal -public class InstrumentationExecutionParameters { - private final ExecutionInput executionInput; - private final String query; - private final String operation; - private final Object context; - private final GraphQLContext graphQLContext; - private final Map variables; - private final InstrumentationState instrumentationState; - private final GraphQLSchema schema; - - public InstrumentationExecutionParameters(ExecutionInput executionInput, GraphQLSchema schema, InstrumentationState instrumentationState) { - this.executionInput = executionInput; - this.query = executionInput.getQuery(); - this.operation = executionInput.getOperationName(); - this.context = executionInput.getContext(); - this.graphQLContext = executionInput.getGraphQLContext(); - this.variables = executionInput.getVariables() != null ? executionInput.getVariables() : ImmutableKit.emptyMap(); - this.instrumentationState = instrumentationState; - this.schema = schema; - } - - /** - * Returns a cloned parameters object with the new state - * - * @param instrumentationState the new state for this parameters object - * - * @return a new parameters object with the new state - */ - public InstrumentationExecutionParameters withNewState(InstrumentationState instrumentationState) { - return new InstrumentationExecutionParameters(this.getExecutionInput(), this.schema, instrumentationState); - } - - public ExecutionInput getExecutionInput() { - return executionInput; - } - - public String getQuery() { - return query; - } - - public String getOperation() { - return operation; - } - - /** - * @param for two - * - * @return the legacy context - * - * @deprecated use {@link #getGraphQLContext()} instead - */ - @Deprecated - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) - public T getContext() { - return (T) context; - } - - public GraphQLContext getGraphQLContext() { - return graphQLContext; - } - - public Map getVariables() { - return variables; - } - - @SuppressWarnings("TypeParameterUnusedInFormals") - public T getInstrumentationState() { - //noinspection unchecked - return (T) instrumentationState; - } - - public GraphQLSchema getSchema() { - return this.schema; - } -} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java b/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java deleted file mode 100644 index ebaa26d3b2..0000000000 --- a/src/main/java/graphql/execution/instrumentation/nextgen/InstrumentationValidationParameters.java +++ /dev/null @@ -1,38 +0,0 @@ -package graphql.execution.instrumentation.nextgen; - -import graphql.ExecutionInput; -import graphql.Internal; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.language.Document; -import graphql.schema.GraphQLSchema; - -/** - * Parameters sent to {@link graphql.execution.instrumentation.nextgen.Instrumentation} methods - */ -@Internal -public class InstrumentationValidationParameters extends InstrumentationExecutionParameters { - private final Document document; - - public InstrumentationValidationParameters(ExecutionInput executionInput, Document document, GraphQLSchema schema, InstrumentationState instrumentationState) { - super(executionInput, schema, instrumentationState); - this.document = document; - } - - /** - * Returns a cloned parameters object with the new state - * - * @param instrumentationState the new state for this parameters object - * - * @return a new parameters object with the new state - */ - @Override - public InstrumentationValidationParameters withNewState(InstrumentationState instrumentationState) { - return new InstrumentationValidationParameters( - this.getExecutionInput(), document, getSchema(), instrumentationState); - } - - - public Document getDocument() { - return document; - } -} diff --git a/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java b/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java deleted file mode 100644 index 2f08b060ba..0000000000 --- a/src/main/java/graphql/execution/instrumentation/nextgen/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * WARNING: All code in this package is a work in progress for a new execution engine. - */ -package graphql.execution.instrumentation.nextgen; diff --git a/src/main/java/graphql/execution/nextgen/BatchedDataFetcher.java b/src/main/java/graphql/execution/nextgen/BatchedDataFetcher.java deleted file mode 100644 index b95954fd63..0000000000 --- a/src/main/java/graphql/execution/nextgen/BatchedDataFetcher.java +++ /dev/null @@ -1,12 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.schema.DataFetcher; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public interface BatchedDataFetcher extends DataFetcher { -} diff --git a/src/main/java/graphql/execution/nextgen/BatchedExecutionStrategy.java b/src/main/java/graphql/execution/nextgen/BatchedExecutionStrategy.java deleted file mode 100644 index a8e1f808fe..0000000000 --- a/src/main/java/graphql/execution/nextgen/BatchedExecutionStrategy.java +++ /dev/null @@ -1,161 +0,0 @@ -package graphql.execution.nextgen; - -import com.google.common.collect.ImmutableList; -import graphql.ExecutionResult; -import graphql.Internal; -import graphql.execution.Async; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.ExecutionStepInfoFactory; -import graphql.execution.FetchedValue; -import graphql.execution.MergedField; -import graphql.execution.MergedSelectionSet; -import graphql.execution.nextgen.result.ExecutionResultNode; -import graphql.execution.nextgen.result.ObjectExecutionResultNode; -import graphql.execution.nextgen.result.ResultNodesUtil; -import graphql.execution.nextgen.result.RootExecutionResultNode; -import graphql.execution.nextgen.result.UnresolvedObjectResultNode; -import graphql.util.FpKit; -import graphql.util.NodeMultiZipper; -import graphql.util.NodeZipper; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import static graphql.Assert.assertNotEmpty; -import static graphql.Assert.assertTrue; -import static graphql.collect.ImmutableKit.map; -import static graphql.execution.nextgen.result.ResultNodeAdapter.RESULT_NODE_ADAPTER; -import static graphql.util.FpKit.flatList; -import static graphql.util.FpKit.mapEntries; -import static graphql.util.FpKit.transposeMatrix; -import static java.util.concurrent.CompletableFuture.completedFuture; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class BatchedExecutionStrategy implements ExecutionStrategy { - - ExecutionStepInfoFactory executionInfoFactory = new ExecutionStepInfoFactory(); - ValueFetcher valueFetcher = new ValueFetcher(); - - FetchedValueAnalyzer fetchedValueAnalyzer = new FetchedValueAnalyzer(); - ExecutionStrategyUtil util = new ExecutionStrategyUtil(); - ExecutionHelper executionHelper = new ExecutionHelper(); - - - @Override - public CompletableFuture execute(ExecutionContext context) { - FieldSubSelection fieldSubSelection = executionHelper.getFieldSubSelection(context); - return executeImpl(context, fieldSubSelection) - .thenApply(ResultNodesUtil::toExecutionResult); - } - - - public CompletableFuture executeImpl(ExecutionContext executionContext, FieldSubSelection fieldSubSelection) { - CompletableFuture rootCF = Async.each(util.fetchSubSelection(executionContext, fieldSubSelection)) - .thenApply(RootExecutionResultNode::new); - - return rootCF.thenCompose(rootNode -> { - NodeMultiZipper unresolvedNodes = ResultNodesUtil.getUnresolvedNodes(rootNode); - return nextStep(executionContext, unresolvedNodes); - }) - .thenApply(multiZipper -> multiZipper.toRootNode()) - .thenApply(RootExecutionResultNode.class::cast); - } - - - private CompletableFuture> nextStep(ExecutionContext executionContext, NodeMultiZipper multizipper) { - NodeMultiZipper nextUnresolvedNodes = ResultNodesUtil.getUnresolvedNodes(multizipper.toRootNode()); - if (nextUnresolvedNodes.getZippers().size() == 0) { - return completedFuture(nextUnresolvedNodes); - } - List> groups = groupNodesIntoBatches(nextUnresolvedNodes); - return resolveNodes(executionContext, groups).thenCompose(next -> nextStep(executionContext, next)); - } - - // all multizipper have the same root - private CompletableFuture> resolveNodes(ExecutionContext executionContext, List> unresolvedNodes) { - assertNotEmpty(unresolvedNodes, () -> "unresolvedNodes can't be empty"); - ExecutionResultNode commonRoot = unresolvedNodes.get(0).getCommonRoot(); - CompletableFuture>>> listListCF = Async.flatMap(unresolvedNodes, - executionResultMultiZipper -> fetchAndAnalyze(executionContext, executionResultMultiZipper.getZippers())); - - return flatList(listListCF).thenApply(zippers -> new NodeMultiZipper(commonRoot, zippers, RESULT_NODE_ADAPTER)); - } - - private List> groupNodesIntoBatches(NodeMultiZipper unresolvedZipper) { - Map>> zipperBySubSelection = FpKit.groupingBy(unresolvedZipper.getZippers(), - (executionResultZipper -> executionResultZipper.getCurNode().getMergedField())); - return mapEntries(zipperBySubSelection, (key, value) -> new NodeMultiZipper(unresolvedZipper.getCommonRoot(), value, RESULT_NODE_ADAPTER)); - } - - private CompletableFuture>> fetchAndAnalyze(ExecutionContext executionContext, List> unresolvedNodes) { - assertTrue(unresolvedNodes.size() > 0, () -> "unresolvedNodes can't be empty"); - - List fieldSubSelections = map(unresolvedNodes, - node -> util.createFieldSubSelection(executionContext, node.getCurNode().getExecutionStepInfo(), node.getCurNode().getResolvedValue())); - - //constrain: all fieldSubSelections have the same mergedSelectionSet - MergedSelectionSet mergedSelectionSet = fieldSubSelections.get(0).getMergedSelectionSet(); - - List>> fetchedValues = batchFetchForEachSubField(executionContext, fieldSubSelections, mergedSelectionSet); - - return mapBatchedResultsBack(unresolvedNodes, fetchedValues); - } - - private CompletableFuture>> mapBatchedResultsBack(List> unresolvedNodes, List>> fetchedValues) { - return Async.each(fetchedValues).thenApply(fetchedValuesMatrix -> { - List> result = new ArrayList<>(); - List> newChildsPerNode = transposeMatrix(fetchedValuesMatrix); - - for (int i = 0; i < newChildsPerNode.size(); i++) { - NodeZipper unresolvedNodeZipper = unresolvedNodes.get(i); - List fetchedValuesForNode = newChildsPerNode.get(i); - NodeZipper resolvedZipper = resolveZipper(unresolvedNodeZipper, fetchedValuesForNode); - result.add(resolvedZipper); - } - return result; - }); - } - - private List>> batchFetchForEachSubField(ExecutionContext executionContext, - List fieldSubSelections, - MergedSelectionSet mergedSelectionSet) { - List sources = map(fieldSubSelections, FieldSubSelection::getSource); - return mapEntries(mergedSelectionSet.getSubFields(), (name, mergedField) -> { - List newExecutionStepInfos = newExecutionInfos(executionContext, fieldSubSelections, mergedField); - return valueFetcher - .fetchBatchedValues(executionContext, sources, mergedField, newExecutionStepInfos) - .thenApply(fetchValue -> analyseValues(executionContext, fetchValue, newExecutionStepInfos)); - }); - } - - private List newExecutionInfos(ExecutionContext executionContext, List fieldSubSelections, MergedField mergedField) { - return map(fieldSubSelections, - subSelection -> executionInfoFactory.newExecutionStepInfoForSubField(executionContext, mergedField, subSelection.getExecutionStepInfo())); - } - - private NodeZipper resolveZipper(NodeZipper unresolvedNodeZipper, List fetchedValuesForNode) { - UnresolvedObjectResultNode unresolvedNode = (UnresolvedObjectResultNode) unresolvedNodeZipper.getCurNode(); - List newChildren = util.fetchedValueAnalysisToNodes(fetchedValuesForNode); - ObjectExecutionResultNode newNode = unresolvedNode.withNewChildren(newChildren); - return unresolvedNodeZipper.withNewNode(newNode); - } - - - private List analyseValues(ExecutionContext executionContext, List fetchedValues, List executionInfos) { - List result = new ArrayList<>(); - for (int i = 0; i < fetchedValues.size(); i++) { - FetchedValue fetchedValue = fetchedValues.get(i); - ExecutionStepInfo executionStepInfo = executionInfos.get(i); - FetchedValueAnalysis fetchedValueAnalysis = fetchedValueAnalyzer.analyzeFetchedValue(executionContext, fetchedValue, executionStepInfo); - result.add(fetchedValueAnalysis); - } - return result; - } -} diff --git a/src/main/java/graphql/execution/nextgen/Common.java b/src/main/java/graphql/execution/nextgen/Common.java deleted file mode 100644 index 049774ae7b..0000000000 --- a/src/main/java/graphql/execution/nextgen/Common.java +++ /dev/null @@ -1,41 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.execution.MissingRootTypeException; -import graphql.language.OperationDefinition; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; - -import java.util.Optional; - -import static graphql.Assert.assertShouldNeverHappen; -import static graphql.language.OperationDefinition.Operation.MUTATION; -import static graphql.language.OperationDefinition.Operation.QUERY; -import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class Common { - - public static GraphQLObjectType getOperationRootType(GraphQLSchema graphQLSchema, OperationDefinition operationDefinition) { - OperationDefinition.Operation operation = operationDefinition.getOperation(); - if (operation == MUTATION) { - GraphQLObjectType mutationType = graphQLSchema.getMutationType(); - return Optional.ofNullable(mutationType) - .orElseThrow(() -> new MissingRootTypeException("Schema is not configured for mutations.", operationDefinition.getSourceLocation())); - } else if (operation == QUERY) { - GraphQLObjectType queryType = graphQLSchema.getQueryType(); - return Optional.ofNullable(queryType) - .orElseThrow(() -> new MissingRootTypeException("Schema does not define the required query root type.", operationDefinition.getSourceLocation())); - } else if (operation == SUBSCRIPTION) { - GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType(); - return Optional.ofNullable(subscriptionType) - .orElseThrow(() -> new MissingRootTypeException("Schema is not configured for subscriptions.", operationDefinition.getSourceLocation())); - } else { - return assertShouldNeverHappen("Unhandled case. An extra operation enum has been added without code support"); - } - } -} diff --git a/src/main/java/graphql/execution/nextgen/DefaultExecutionStrategy.java b/src/main/java/graphql/execution/nextgen/DefaultExecutionStrategy.java deleted file mode 100644 index b01f3709bd..0000000000 --- a/src/main/java/graphql/execution/nextgen/DefaultExecutionStrategy.java +++ /dev/null @@ -1,80 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.ExecutionResult; -import graphql.Internal; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.nextgen.result.ExecutionResultNode; -import graphql.execution.nextgen.result.ObjectExecutionResultNode; -import graphql.execution.nextgen.result.ResolvedValue; -import graphql.execution.nextgen.result.ResultNodesUtil; -import graphql.execution.nextgen.result.RootExecutionResultNode; -import graphql.util.NodeMultiZipper; -import graphql.util.NodeZipper; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static graphql.collect.ImmutableKit.map; -import static graphql.execution.Async.each; -import static graphql.execution.Async.mapCompose; - -/** - * - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class DefaultExecutionStrategy implements ExecutionStrategy { - - ExecutionStrategyUtil util = new ExecutionStrategyUtil(); - ExecutionHelper executionHelper = new ExecutionHelper(); - - @Override - public CompletableFuture execute(ExecutionContext context) { - FieldSubSelection fieldSubSelection = executionHelper.getFieldSubSelection(context); - return executeImpl(context, fieldSubSelection) - .thenApply(ResultNodesUtil::toExecutionResult); - } - - /* - * the fundamental algorithm is: - * - fetch sub selection and analyze it - * - convert the fetched value analysis into result node - * - get all unresolved result nodes and resolve the sub selection (start again recursively) - */ - public CompletableFuture executeImpl(ExecutionContext context, FieldSubSelection fieldSubSelection) { - return resolveSubSelection(context, fieldSubSelection) - .thenApply(RootExecutionResultNode::new); - } - - private CompletableFuture> resolveSubSelection(ExecutionContext executionContext, FieldSubSelection fieldSubSelection) { - List> namedNodesCFList = - mapCompose(util.fetchSubSelection(executionContext, fieldSubSelection), node -> resolveAllChildNodes(executionContext, node)); - return each(namedNodesCFList); - } - - private CompletableFuture resolveAllChildNodes(ExecutionContext context, ExecutionResultNode node) { - NodeMultiZipper unresolvedNodes = ResultNodesUtil.getUnresolvedNodes(node); - List>> resolvedNodes = map(unresolvedNodes.getZippers(), unresolvedNode -> resolveNode(context, unresolvedNode)); - return resolvedNodesToResultNode(unresolvedNodes, resolvedNodes); - } - - private CompletableFuture> resolveNode(ExecutionContext executionContext, NodeZipper unresolvedNode) { - ExecutionStepInfo executionStepInfo = unresolvedNode.getCurNode().getExecutionStepInfo(); - ResolvedValue resolvedValue = unresolvedNode.getCurNode().getResolvedValue(); - FieldSubSelection fieldSubSelection = util.createFieldSubSelection(executionContext, executionStepInfo, resolvedValue); - return resolveSubSelection(executionContext, fieldSubSelection) - .thenApply(resolvedChildMap -> unresolvedNode.withNewNode(new ObjectExecutionResultNode(executionStepInfo, resolvedValue, resolvedChildMap))); - } - - private CompletableFuture resolvedNodesToResultNode( - NodeMultiZipper unresolvedNodes, - List>> resolvedNodes) { - return each(resolvedNodes) - .thenApply(unresolvedNodes::withReplacedZippers) - .thenApply(NodeMultiZipper::toRootNode); - } - - -} diff --git a/src/main/java/graphql/execution/nextgen/Execution.java b/src/main/java/graphql/execution/nextgen/Execution.java deleted file mode 100644 index 4e39298e59..0000000000 --- a/src/main/java/graphql/execution/nextgen/Execution.java +++ /dev/null @@ -1,48 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.ExecutionResultImpl; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.execution.Async; -import graphql.execution.ExecutionId; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.language.Document; -import graphql.schema.GraphQLSchema; - -import java.util.concurrent.CompletableFuture; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class Execution { - - ExecutionHelper executionHelper = new ExecutionHelper(); - - public CompletableFuture execute(ExecutionStrategy executionStrategy, - Document document, - GraphQLSchema graphQLSchema, - ExecutionId executionId, - ExecutionInput executionInput, - InstrumentationState instrumentationState) { - ExecutionHelper.ExecutionData executionData; - try { - executionData = executionHelper.createExecutionData(document, graphQLSchema, executionId, executionInput, instrumentationState); - } catch (RuntimeException rte) { - if (rte instanceof GraphQLError) { - return CompletableFuture.completedFuture(new ExecutionResultImpl((GraphQLError) rte)); - } - return Async.exceptionallyCompletedFuture(rte); - } - - try { - return executionStrategy - .execute(executionData.executionContext); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/graphql/execution/nextgen/ExecutionHelper.java b/src/main/java/graphql/execution/nextgen/ExecutionHelper.java deleted file mode 100644 index 19f1ed1e22..0000000000 --- a/src/main/java/graphql/execution/nextgen/ExecutionHelper.java +++ /dev/null @@ -1,98 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.ExecutionInput; -import graphql.Internal; -import graphql.execution.CoercedVariables; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionId; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.FieldCollector; -import graphql.execution.FieldCollectorParameters; -import graphql.execution.MergedSelectionSet; -import graphql.execution.RawVariables; -import graphql.execution.ResultPath; -import graphql.execution.ValuesResolver; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.language.Document; -import graphql.language.FragmentDefinition; -import graphql.language.NodeUtil; -import graphql.language.OperationDefinition; -import graphql.language.VariableDefinition; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; - -import java.util.List; -import java.util.Map; - -import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; -import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ExecutionHelper { - - private final FieldCollector fieldCollector = new FieldCollector(); - - public static class ExecutionData { - public ExecutionContext executionContext; - } - - public ExecutionData createExecutionData(Document document, - GraphQLSchema graphQLSchema, - ExecutionId executionId, - ExecutionInput executionInput, - InstrumentationState instrumentationState) { - - NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, executionInput.getOperationName()); - Map fragmentsByName = getOperationResult.fragmentsByName; - OperationDefinition operationDefinition = getOperationResult.operationDefinition; - - RawVariables inputVariables = executionInput.getRawVariables(); - List variableDefinitions = operationDefinition.getVariableDefinitions(); - - CoercedVariables coercedVariables = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, inputVariables); - - ExecutionContext executionContext = newExecutionContextBuilder() - .executionId(executionId) - .instrumentationState(instrumentationState) - .graphQLSchema(graphQLSchema) - .context(executionInput.getContext()) - .graphQLContext(executionInput.getGraphQLContext()) - .root(executionInput.getRoot()) - .fragmentsByName(fragmentsByName) - .coercedVariables(coercedVariables) - .document(document) - .operationDefinition(operationDefinition) - .build(); - - ExecutionData executionData = new ExecutionData(); - executionData.executionContext = executionContext; - return executionData; - } - - public FieldSubSelection getFieldSubSelection(ExecutionContext executionContext) { - OperationDefinition operationDefinition = executionContext.getOperationDefinition(); - GraphQLObjectType operationRootType = Common.getOperationRootType(executionContext.getGraphQLSchema(), operationDefinition); - - FieldCollectorParameters collectorParameters = FieldCollectorParameters.newParameters() - .schema(executionContext.getGraphQLSchema()) - .objectType(operationRootType) - .fragments(executionContext.getFragmentsByName()) - .variables(executionContext.getVariables()) - .build(); - - MergedSelectionSet mergedSelectionSet = fieldCollector.collectFields(collectorParameters, operationDefinition.getSelectionSet()); - ExecutionStepInfo executionInfo = newExecutionStepInfo().type(operationRootType).path(ResultPath.rootPath()).build(); - - FieldSubSelection fieldSubSelection = FieldSubSelection.newFieldSubSelection() - .source(executionContext.getRoot()) - .localContext(executionContext.getLocalContext()) - .mergedSelectionSet(mergedSelectionSet) - .executionInfo(executionInfo) - .build(); - return fieldSubSelection; - } -} diff --git a/src/main/java/graphql/execution/nextgen/ExecutionStrategy.java b/src/main/java/graphql/execution/nextgen/ExecutionStrategy.java deleted file mode 100644 index 70534d66d8..0000000000 --- a/src/main/java/graphql/execution/nextgen/ExecutionStrategy.java +++ /dev/null @@ -1,18 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.ExecutionResult; -import graphql.Internal; -import graphql.execution.ExecutionContext; - -import java.util.concurrent.CompletableFuture; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public interface ExecutionStrategy { - - CompletableFuture execute(ExecutionContext context); - -} diff --git a/src/main/java/graphql/execution/nextgen/ExecutionStrategyUtil.java b/src/main/java/graphql/execution/nextgen/ExecutionStrategyUtil.java deleted file mode 100644 index 51c2e058a0..0000000000 --- a/src/main/java/graphql/execution/nextgen/ExecutionStrategyUtil.java +++ /dev/null @@ -1,102 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.execution.Async; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.ExecutionStepInfoFactory; -import graphql.execution.FetchedValue; -import graphql.execution.FieldCollector; -import graphql.execution.FieldCollectorParameters; -import graphql.execution.MergedField; -import graphql.execution.MergedSelectionSet; -import graphql.execution.ResolveType; -import graphql.execution.nextgen.result.ExecutionResultNode; -import graphql.execution.nextgen.result.ResolvedValue; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLOutputType; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static graphql.collect.ImmutableKit.map; -import static graphql.execution.FieldCollectorParameters.newParameters; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ExecutionStrategyUtil { - - ExecutionStepInfoFactory executionStepInfoFactory = new ExecutionStepInfoFactory(); - FetchedValueAnalyzer fetchedValueAnalyzer = new FetchedValueAnalyzer(); - ValueFetcher valueFetcher = new ValueFetcher(); - ResultNodesCreator resultNodesCreator = new ResultNodesCreator(); - ResolveType resolveType = new ResolveType(); - FieldCollector fieldCollector = new FieldCollector(); - - public List> fetchSubSelection(ExecutionContext executionContext, FieldSubSelection fieldSubSelection) { - List> fetchedValueAnalysisList = fetchAndAnalyze(executionContext, fieldSubSelection); - return fetchedValueAnalysisToNodesAsync(fetchedValueAnalysisList); - } - - private List> fetchAndAnalyze(ExecutionContext context, FieldSubSelection fieldSubSelection) { - - return map(fieldSubSelection.getMergedSelectionSet().getSubFieldsList(), - mergedField -> fetchAndAnalyzeField(context, fieldSubSelection.getSource(), fieldSubSelection.getLocalContext(), mergedField, fieldSubSelection.getExecutionStepInfo())); - - } - - private CompletableFuture fetchAndAnalyzeField(ExecutionContext context, Object source, Object localContext, MergedField mergedField, - ExecutionStepInfo executionStepInfo) { - - ExecutionStepInfo newExecutionStepInfo = executionStepInfoFactory.newExecutionStepInfoForSubField(context, mergedField, executionStepInfo); - return valueFetcher - .fetchValue(context, source, localContext, mergedField, newExecutionStepInfo) - .thenApply(fetchValue -> analyseValue(context, fetchValue, newExecutionStepInfo)); - } - - private List> fetchedValueAnalysisToNodesAsync(List> list) { - return Async.map(list, fetchedValueAnalysis -> resultNodesCreator.createResultNode(fetchedValueAnalysis)); - } - - public List fetchedValueAnalysisToNodes(List fetchedValueAnalysisList) { - return map(fetchedValueAnalysisList, fetchedValueAnalysis -> resultNodesCreator.createResultNode(fetchedValueAnalysis)); - } - - - private FetchedValueAnalysis analyseValue(ExecutionContext executionContext, FetchedValue fetchedValue, ExecutionStepInfo executionInfo) { - FetchedValueAnalysis fetchedValueAnalysis = fetchedValueAnalyzer.analyzeFetchedValue(executionContext, fetchedValue, executionInfo); - return fetchedValueAnalysis; - } - - public FieldSubSelection createFieldSubSelection(ExecutionContext executionContext, ExecutionStepInfo executionInfo, ResolvedValue resolvedValue) { - MergedField field = executionInfo.getField(); - Object source = resolvedValue.getCompletedValue(); - Object localContext = resolvedValue.getLocalContext(); - - GraphQLOutputType sourceType = executionInfo.getUnwrappedNonNullType(); - GraphQLObjectType resolvedObjectType = resolveType.resolveType(executionContext, field, source, executionInfo, sourceType, localContext); - FieldCollectorParameters collectorParameters = newParameters() - .schema(executionContext.getGraphQLSchema()) - .objectType(resolvedObjectType) - .fragments(executionContext.getFragmentsByName()) - .variables(executionContext.getVariables()) - .build(); - MergedSelectionSet subFields = fieldCollector.collectFields(collectorParameters, - executionInfo.getField()); - - // it is not really a new step but rather a refinement - ExecutionStepInfo newExecutionStepInfoWithResolvedType = executionInfo.changeTypeWithPreservedNonNull(resolvedObjectType); - - return FieldSubSelection.newFieldSubSelection() - .source(source) - .localContext(localContext) - .mergedSelectionSet(subFields) - .executionInfo(newExecutionStepInfoWithResolvedType) - .build(); - } - - -} diff --git a/src/main/java/graphql/execution/nextgen/FetchedValueAnalysis.java b/src/main/java/graphql/execution/nextgen/FetchedValueAnalysis.java deleted file mode 100644 index 098da68118..0000000000 --- a/src/main/java/graphql/execution/nextgen/FetchedValueAnalysis.java +++ /dev/null @@ -1,203 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.GraphQLError; -import graphql.Internal; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.FetchedValue; -import graphql.execution.MergedField; -import graphql.schema.GraphQLObjectType; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import static graphql.Assert.assertNotNull; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class FetchedValueAnalysis { - - public enum FetchedValueType { - OBJECT, - LIST, - SCALAR, - ENUM, - } - - private final FetchedValueType valueType; - private final List errors; - // not applicable for LIST - private final Object completedValue; - private final boolean nullValue; - // only available for LIST - private final List children; - // only for object - private final GraphQLObjectType resolvedType; - private final ExecutionStepInfo executionStepInfo; - // for LIST this is the whole list - private final FetchedValue fetchedValue; - - private FetchedValueAnalysis(Builder builder) { - this.errors = new ArrayList<>(builder.errors); - this.errors.addAll(builder.fetchedValue.getErrors()); - this.valueType = assertNotNull(builder.valueType); - this.completedValue = builder.completedValue; - this.nullValue = builder.nullValue; - this.children = builder.children; - this.resolvedType = builder.resolvedType; - this.executionStepInfo = assertNotNull(builder.executionInfo); - this.fetchedValue = assertNotNull(builder.fetchedValue); - } - - public FetchedValueType getValueType() { - return valueType; - } - - public List getErrors() { - return errors; - } - - public Object getCompletedValue() { - return completedValue; - } - - public List getChildren() { - return children; - } - - public boolean isNullValue() { - return nullValue; - } - - public FetchedValue getFetchedValue() { - return fetchedValue; - } - - public FetchedValueAnalysis transform(Consumer builderConsumer) { - Builder builder = new Builder(this); - builderConsumer.accept(builder); - return builder.build(); - } - - public static Builder newFetchedValueAnalysis() { - return new Builder(); - } - - public static Builder newFetchedValueAnalysis(FetchedValueType valueType) { - return new Builder().valueType(valueType); - } - - public static Builder newFetchedValueAnalysis(FetchedValueAnalysis existing) { - return new Builder(existing); - } - - public ExecutionStepInfo getExecutionStepInfo() { - return executionStepInfo; - } - - public GraphQLObjectType getResolvedType() { - return resolvedType; - } - - public MergedField getField() { - return executionStepInfo.getField(); - } - - public String getResultKey() { - return executionStepInfo.getResultKey(); - } - - @Override - public String toString() { - return "{" + - "valueType=" + valueType + - ", completedValue=" + completedValue + - ", errors=" + errors + - ", children=" + children + - ", stepInfo=" + executionStepInfo + - ", nullValue=" + nullValue + - ", resolvedType=" + resolvedType + - ", fetchedValue=" + fetchedValue + - '}'; - } - - public static final class Builder { - private FetchedValueType valueType; - private final List errors = new ArrayList<>(); - private Object completedValue; - private FetchedValue fetchedValue; - private List children; - private GraphQLObjectType resolvedType; - private boolean nullValue; - private ExecutionStepInfo executionInfo; - - private Builder() { - } - - private Builder(FetchedValueAnalysis existing) { - valueType = existing.getValueType(); - errors.addAll(existing.getErrors()); - completedValue = existing.getCompletedValue(); - fetchedValue = existing.getFetchedValue(); - children = existing.getChildren(); - nullValue = existing.isNullValue(); - resolvedType = existing.getResolvedType(); - executionInfo = existing.getExecutionStepInfo(); - } - - - public Builder valueType(FetchedValueType val) { - valueType = val; - return this; - } - - public Builder errors(List errors) { - this.errors.addAll(errors); - return this; - } - - public Builder error(GraphQLError error) { - this.errors.add(error); - return this; - } - - - public Builder completedValue(Object completedValue) { - this.completedValue = completedValue; - return this; - } - - public Builder children(List children) { - this.children = children; - return this; - } - - - public Builder nullValue() { - this.nullValue = true; - return this; - } - - public Builder resolvedType(GraphQLObjectType resolvedType) { - this.resolvedType = resolvedType; - return this; - } - - public Builder executionStepInfo(ExecutionStepInfo executionInfo) { - this.executionInfo = executionInfo; - return this; - } - - public Builder fetchedValue(FetchedValue fetchedValue) { - this.fetchedValue = fetchedValue; - return this; - } - - public FetchedValueAnalysis build() { - return new FetchedValueAnalysis(this); - } - } -} diff --git a/src/main/java/graphql/execution/nextgen/FetchedValueAnalyzer.java b/src/main/java/graphql/execution/nextgen/FetchedValueAnalyzer.java deleted file mode 100644 index 1b93889972..0000000000 --- a/src/main/java/graphql/execution/nextgen/FetchedValueAnalyzer.java +++ /dev/null @@ -1,214 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.SerializationError; -import graphql.TypeMismatchError; -import graphql.UnresolvedTypeError; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.ExecutionStepInfoFactory; -import graphql.execution.FetchedValue; -import graphql.execution.MergedField; -import graphql.execution.NonNullableFieldWasNullException; -import graphql.execution.ResolveType; -import graphql.execution.UnresolvedTypeException; -import graphql.schema.CoercingSerializeException; -import graphql.schema.GraphQLEnumType; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLScalarType; -import graphql.schema.GraphQLType; -import graphql.util.FpKit; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static graphql.execution.nextgen.FetchedValueAnalysis.FetchedValueType.ENUM; -import static graphql.execution.nextgen.FetchedValueAnalysis.FetchedValueType.LIST; -import static graphql.execution.nextgen.FetchedValueAnalysis.FetchedValueType.OBJECT; -import static graphql.execution.nextgen.FetchedValueAnalysis.FetchedValueType.SCALAR; -import static graphql.execution.nextgen.FetchedValueAnalysis.newFetchedValueAnalysis; -import static graphql.schema.GraphQLTypeUtil.isList; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class FetchedValueAnalyzer { - - ExecutionStepInfoFactory executionInfoFactory = new ExecutionStepInfoFactory(); - ResolveType resolveType = new ResolveType(); - - - /* - * scalar: the value, null and/or error - * enum: same as scalar - * list: list of X: X can be list again, list of scalars or enum or objects - */ - public FetchedValueAnalysis analyzeFetchedValue(ExecutionContext executionContext, FetchedValue fetchedValue, ExecutionStepInfo executionInfo) throws NonNullableFieldWasNullException { - return analyzeFetchedValueImpl(executionContext, fetchedValue, fetchedValue.getFetchedValue(), executionInfo); - } - - private FetchedValueAnalysis analyzeFetchedValueImpl(ExecutionContext executionContext, FetchedValue fetchedValue, Object toAnalyze, ExecutionStepInfo executionInfo) throws NonNullableFieldWasNullException { - GraphQLType fieldType = executionInfo.getUnwrappedNonNullType(); - MergedField field = executionInfo.getField(); - - if (isList(fieldType)) { - return analyzeList(executionContext, fetchedValue, toAnalyze, executionInfo); - } else if (fieldType instanceof GraphQLScalarType) { - return analyzeScalarValue(fetchedValue, toAnalyze, (GraphQLScalarType) fieldType, executionInfo); - } else if (fieldType instanceof GraphQLEnumType) { - return analyzeEnumValue(fetchedValue, toAnalyze, (GraphQLEnumType) fieldType, executionInfo); - } - - // when we are here, we have a complex type: Interface, Union or Object - // and we must go deeper - // - if (toAnalyze == null) { - return newFetchedValueAnalysis(OBJECT) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .build(); - } - try { - GraphQLObjectType resolvedObjectType = resolveType.resolveType(executionContext, field, toAnalyze, executionInfo, fieldType, fetchedValue.getLocalContext()); - return newFetchedValueAnalysis(OBJECT) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .completedValue(toAnalyze) - .resolvedType(resolvedObjectType) - .build(); - } catch (UnresolvedTypeException ex) { - return handleUnresolvedTypeProblem(fetchedValue, executionInfo, ex); - } - } - - - private FetchedValueAnalysis handleUnresolvedTypeProblem(FetchedValue fetchedValue, ExecutionStepInfo executionInfo, UnresolvedTypeException e) { - UnresolvedTypeError error = new UnresolvedTypeError(executionInfo.getPath(), executionInfo, e); - return newFetchedValueAnalysis(OBJECT) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .error(error) - .build(); - } - - private FetchedValueAnalysis analyzeList(ExecutionContext executionContext, FetchedValue fetchedValue, Object toAnalyze, ExecutionStepInfo executionInfo) { - if (toAnalyze == null) { - return newFetchedValueAnalysis(LIST) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .build(); - } - - if (toAnalyze.getClass().isArray() || toAnalyze instanceof Iterable) { - Collection collection = FpKit.toCollection(toAnalyze); - return analyzeIterable(executionContext, fetchedValue, collection, executionInfo); - } else { - TypeMismatchError error = new TypeMismatchError(executionInfo.getPath(), executionInfo.getType()); - return newFetchedValueAnalysis(LIST) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .error(error) - .build(); - } - - } - - private FetchedValueAnalysis analyzeIterable(ExecutionContext executionContext, FetchedValue fetchedValue, Iterable iterableValues, ExecutionStepInfo executionInfo) { - - Collection values = FpKit.toCollection(iterableValues); - List children = new ArrayList<>(); - int index = 0; - for (Object item : values) { - ExecutionStepInfo executionInfoForListElement = executionInfoFactory.newExecutionStepInfoForListElement(executionInfo, index); - children.add(analyzeFetchedValueImpl(executionContext, fetchedValue, item, executionInfoForListElement)); - index++; - } - return newFetchedValueAnalysis(LIST) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .children(children) - .build(); - - } - - - private FetchedValueAnalysis analyzeScalarValue(FetchedValue fetchedValue, Object toAnalyze, GraphQLScalarType scalarType, ExecutionStepInfo executionInfo) { - if (toAnalyze == null) { - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .build(); - } - Object serialized; - try { - serialized = serializeScalarValue(toAnalyze, scalarType); - } catch (CoercingSerializeException e) { - SerializationError error = new SerializationError(executionInfo.getPath(), e); - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .error(error) - .nullValue() - .build(); - } - - // TODO: fix that: this should not be handled here - //6.6.1 http://facebook.github.io/graphql/#sec-Field-entries - if (serialized instanceof Double && ((Double) serialized).isNaN()) { - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .build(); - } - // handle non null - - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .completedValue(serialized) - .build(); - } - - protected Object serializeScalarValue(Object toAnalyze, GraphQLScalarType scalarType) throws CoercingSerializeException { - return scalarType.getCoercing().serialize(toAnalyze); - } - - private FetchedValueAnalysis analyzeEnumValue(FetchedValue fetchedValue, Object toAnalyze, GraphQLEnumType enumType, ExecutionStepInfo executionInfo) { - if (toAnalyze == null) { - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .build(); - - } - Object serialized; - try { - serialized = enumType.serialize(toAnalyze); - } catch (CoercingSerializeException e) { - SerializationError error = new SerializationError(executionInfo.getPath(), e); - return newFetchedValueAnalysis(SCALAR) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .nullValue() - .error(error) - .build(); - } - // handle non null values - return newFetchedValueAnalysis(ENUM) - .fetchedValue(fetchedValue) - .executionStepInfo(executionInfo) - .completedValue(serialized) - .build(); - } - -} diff --git a/src/main/java/graphql/execution/nextgen/FieldSubSelection.java b/src/main/java/graphql/execution/nextgen/FieldSubSelection.java deleted file mode 100644 index ac3407a4a1..0000000000 --- a/src/main/java/graphql/execution/nextgen/FieldSubSelection.java +++ /dev/null @@ -1,100 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.MergedField; -import graphql.execution.MergedSelectionSet; - -import java.util.Map; - - -/** - * A map from name to List of Field representing the actual sub selections (during execution) of a Field with Fragments - * evaluated and conditional directives considered. - * - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class FieldSubSelection { - - private final Object source; - private final Object localContext; - // the type of this must be objectType and is the parent executionStepInfo for all mergedSelectionSet - private final ExecutionStepInfo executionInfo; - private final MergedSelectionSet mergedSelectionSet; - - private FieldSubSelection(Builder builder) { - this.source = builder.source; - this.localContext = builder.localContext; - this.executionInfo = builder.executionInfo; - this.mergedSelectionSet = builder.mergedSelectionSet; - } - - public Object getSource() { - return source; - } - - public Object getLocalContext() { - return localContext; - } - - public Map getSubFields() { - return mergedSelectionSet.getSubFields(); - } - - public MergedSelectionSet getMergedSelectionSet() { - return mergedSelectionSet; - } - - public ExecutionStepInfo getExecutionStepInfo() { - return executionInfo; - } - - @Override - public String toString() { - return "FieldSubSelection{" + - "source=" + source + - ", executionInfo=" + executionInfo + - ", mergedSelectionSet" + mergedSelectionSet + - '}'; - } - - public static Builder newFieldSubSelection() { - return new Builder(); - } - - public static class Builder { - private Object source; - private Object localContext; - private ExecutionStepInfo executionInfo; - private MergedSelectionSet mergedSelectionSet; - - public Builder source(Object source) { - this.source = source; - return this; - } - - public Builder localContext(Object localContext) { - this.localContext = localContext; - return this; - } - - public Builder executionInfo(ExecutionStepInfo executionInfo) { - this.executionInfo = executionInfo; - return this; - } - - public Builder mergedSelectionSet(MergedSelectionSet mergedSelectionSet) { - this.mergedSelectionSet = mergedSelectionSet; - return this; - } - - public FieldSubSelection build() { - return new FieldSubSelection(this); - } - - - } - -} diff --git a/src/main/java/graphql/execution/nextgen/ResultNodesCreator.java b/src/main/java/graphql/execution/nextgen/ResultNodesCreator.java deleted file mode 100644 index 4366f875d0..0000000000 --- a/src/main/java/graphql/execution/nextgen/ResultNodesCreator.java +++ /dev/null @@ -1,70 +0,0 @@ -package graphql.execution.nextgen; - -import graphql.Internal; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.NonNullableFieldWasNullException; -import graphql.execution.nextgen.result.ExecutionResultNode; -import graphql.execution.nextgen.result.LeafExecutionResultNode; -import graphql.execution.nextgen.result.ListExecutionResultNode; -import graphql.execution.nextgen.result.ResolvedValue; -import graphql.execution.nextgen.result.UnresolvedObjectResultNode; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import static graphql.collect.ImmutableKit.map; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ResultNodesCreator { - - public ExecutionResultNode createResultNode(FetchedValueAnalysis fetchedValueAnalysis) { - ResolvedValue resolvedValue = createResolvedValue(fetchedValueAnalysis); - ExecutionStepInfo executionStepInfo = fetchedValueAnalysis.getExecutionStepInfo(); - - if (fetchedValueAnalysis.isNullValue() && executionStepInfo.isNonNullType()) { - NonNullableFieldWasNullException nonNullableFieldWasNullException = new NonNullableFieldWasNullException(executionStepInfo, executionStepInfo.getPath()); - - return new LeafExecutionResultNode(executionStepInfo, resolvedValue, nonNullableFieldWasNullException); - } - if (fetchedValueAnalysis.isNullValue()) { - return new LeafExecutionResultNode(executionStepInfo, resolvedValue, null); - } - if (fetchedValueAnalysis.getValueType() == FetchedValueAnalysis.FetchedValueType.OBJECT) { - return createUnresolvedNode(fetchedValueAnalysis); - } - if (fetchedValueAnalysis.getValueType() == FetchedValueAnalysis.FetchedValueType.LIST) { - return createListResultNode(fetchedValueAnalysis); - } - return new LeafExecutionResultNode(executionStepInfo, resolvedValue, null); - } - - private ExecutionResultNode createUnresolvedNode(FetchedValueAnalysis fetchedValueAnalysis) { - return new UnresolvedObjectResultNode(fetchedValueAnalysis.getExecutionStepInfo(), createResolvedValue(fetchedValueAnalysis)); - } - - private ResolvedValue createResolvedValue(FetchedValueAnalysis fetchedValueAnalysis) { - return ResolvedValue.newResolvedValue() - .completedValue(fetchedValueAnalysis.getCompletedValue()) - .localContext(fetchedValueAnalysis.getFetchedValue().getLocalContext()) - .nullValue(fetchedValueAnalysis.isNullValue()) - .errors(fetchedValueAnalysis.getErrors()) - .build(); - } - - private Optional getFirstNonNullableException(Collection collection) { - return collection.stream() - .filter(executionResultNode -> executionResultNode.getNonNullableFieldWasNullException() != null) - .map(ExecutionResultNode::getNonNullableFieldWasNullException) - .findFirst(); - } - - private ExecutionResultNode createListResultNode(FetchedValueAnalysis fetchedValueAnalysis) { - List executionResultNodes = map(fetchedValueAnalysis.getChildren(), this::createResultNode); - return new ListExecutionResultNode(fetchedValueAnalysis.getExecutionStepInfo(), createResolvedValue(fetchedValueAnalysis), executionResultNodes); - } -} diff --git a/src/main/java/graphql/execution/nextgen/ValueFetcher.java b/src/main/java/graphql/execution/nextgen/ValueFetcher.java deleted file mode 100644 index 0eddc24274..0000000000 --- a/src/main/java/graphql/execution/nextgen/ValueFetcher.java +++ /dev/null @@ -1,230 +0,0 @@ -package graphql.execution.nextgen; - - -import com.google.common.collect.ImmutableList; -import graphql.Assert; -import graphql.ExceptionWhileDataFetching; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.Async; -import graphql.execution.DataFetcherResult; -import graphql.execution.DefaultValueUnboxer; -import graphql.execution.ExecutionContext; -import graphql.execution.ExecutionId; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.FetchedValue; -import graphql.execution.MergedField; -import graphql.execution.ResultPath; -import graphql.execution.ValuesResolver; -import graphql.execution.directives.QueryDirectivesImpl; -import graphql.language.Field; -import graphql.normalized.ExecutableNormalizedField; -import graphql.normalized.ExecutableNormalizedOperation; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.DataFetchingFieldSelectionSet; -import graphql.schema.DataFetchingFieldSelectionSetImpl; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLFieldsContainer; -import graphql.schema.GraphQLOutputType; -import graphql.schema.GraphQLTypeUtil; -import graphql.util.FpKit; -import graphql.util.LogKit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import static graphql.schema.DataFetchingEnvironmentImpl.newDataFetchingEnvironment; -import static java.util.Collections.singletonList; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ValueFetcher { - private static final Logger log = LoggerFactory.getLogger(ValueFetcher.class); - private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(ExecutionStrategy.class); - - public static final Object NULL_VALUE = new Object(); - - public ValueFetcher() { - } - - - public CompletableFuture> fetchBatchedValues(ExecutionContext executionContext, List sources, MergedField field, List executionInfos) { - ExecutionStepInfo executionStepInfo = executionInfos.get(0); - // TODO - add support for field context to batching code - Object todoLocalContext = null; - if (isDataFetcherBatched(executionContext, executionStepInfo)) { - return fetchValue(executionContext, sources, todoLocalContext, field, executionStepInfo) - .thenApply(fetchedValue -> extractBatchedValues(fetchedValue, sources.size())); - } else { - List> fetchedValues = new ArrayList<>(); - for (int i = 0; i < sources.size(); i++) { - fetchedValues.add(fetchValue(executionContext, sources.get(i), todoLocalContext, field, executionInfos.get(i))); - } - return Async.each(fetchedValues); - } - } - - @SuppressWarnings("unchecked") - private List extractBatchedValues(FetchedValue fetchedValueContainingList, int expectedSize) { - List list = (List) fetchedValueContainingList.getFetchedValue(); - Assert.assertTrue(list.size() == expectedSize, () -> "Unexpected result size"); - List result = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - List errors; - if (i == 0) { - errors = fetchedValueContainingList.getErrors(); - } else { - errors = ImmutableKit.emptyList(); - } - FetchedValue fetchedValue = FetchedValue.newFetchedValue() - .fetchedValue(list.get(i)) - .rawFetchedValue(fetchedValueContainingList.getRawFetchedValue()) - .errors(errors) - .localContext(fetchedValueContainingList.getLocalContext()) - .build(); - result.add(fetchedValue); - } - return result; - } - - private GraphQLFieldsContainer getFieldsContainer(ExecutionStepInfo executionStepInfo) { - GraphQLOutputType type = executionStepInfo.getParent().getType(); - return (GraphQLFieldsContainer) GraphQLTypeUtil.unwrapAll(type); - } - - private boolean isDataFetcherBatched(ExecutionContext executionContext, ExecutionStepInfo executionStepInfo) { - GraphQLFieldsContainer parentType = getFieldsContainer(executionStepInfo); - GraphQLFieldDefinition fieldDef = executionStepInfo.getFieldDefinition(); - DataFetcher dataFetcher = executionContext.getGraphQLSchema().getCodeRegistry().getDataFetcher(parentType, fieldDef); - return dataFetcher instanceof BatchedDataFetcher; - } - - public CompletableFuture fetchValue(ExecutionContext executionContext, Object source, Object localContext, MergedField sameFields, ExecutionStepInfo executionInfo) { - Field field = sameFields.getSingleField(); - GraphQLFieldDefinition fieldDef = executionInfo.getFieldDefinition(); - - GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); - GraphQLFieldsContainer parentType = getFieldsContainer(executionInfo); - - Supplier> argumentValues = FpKit.intraThreadMemoize(() -> ValuesResolver.getArgumentValues(codeRegistry, fieldDef.getArguments(), field.getArguments(), executionContext.getCoercedVariables())); - - QueryDirectivesImpl queryDirectives = new QueryDirectivesImpl(sameFields, executionContext.getGraphQLSchema(), executionContext.getVariables()); - - GraphQLOutputType fieldType = fieldDef.getType(); - - Supplier normalizedQuery = executionContext.getNormalizedQueryTree(); - Supplier normalisedField = () -> normalizedQuery.get().getNormalizedField(sameFields, executionInfo.getObjectType(), executionInfo.getPath()); - DataFetchingFieldSelectionSet selectionSet = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalisedField); - - DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext) - .source(source) - .localContext(localContext) - .arguments(argumentValues) - .fieldDefinition(fieldDef) - .mergedField(sameFields) - .fieldType(fieldType) - .executionStepInfo(executionInfo) - .parentType(parentType) - .selectionSet(selectionSet) - .queryDirectives(queryDirectives) - .build(); - - ExecutionId executionId = executionContext.getExecutionId(); - ResultPath path = executionInfo.getPath(); - return callDataFetcher(codeRegistry, parentType, fieldDef, environment, executionId, path) - .thenApply(rawFetchedValue -> FetchedValue.newFetchedValue() - .fetchedValue(rawFetchedValue) - .rawFetchedValue(rawFetchedValue) - .build()) - .exceptionally(exception -> handleExceptionWhileFetching(field, path, exception)) - .thenApply(result -> unboxPossibleDataFetcherResult(sameFields, path, result, localContext)) - .thenApply(this::unboxPossibleOptional); - } - - private FetchedValue handleExceptionWhileFetching(Field field, ResultPath path, Throwable exception) { - ExceptionWhileDataFetching exceptionWhileDataFetching = new ExceptionWhileDataFetching(path, exception, field.getSourceLocation()); - return FetchedValue.newFetchedValue().errors(singletonList(exceptionWhileDataFetching)).build(); - } - - private FetchedValue unboxPossibleOptional(FetchedValue result) { - return result.transform( - builder -> builder.fetchedValue(DefaultValueUnboxer.unboxValue(result.getFetchedValue())) - ); - } - - private CompletableFuture callDataFetcher(GraphQLCodeRegistry codeRegistry, GraphQLFieldsContainer parentType, GraphQLFieldDefinition fieldDef, DataFetchingEnvironment environment, ExecutionId executionId, ResultPath path) { - CompletableFuture result = new CompletableFuture<>(); - try { - DataFetcher dataFetcher = codeRegistry.getDataFetcher(parentType, fieldDef); - if (log.isDebugEnabled()) { - log.debug("'{}' fetching field '{}' using data fetcher '{}'...", executionId, path, dataFetcher.getClass().getName()); - } - Object fetchedValueRaw = dataFetcher.get(environment); - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("'{}' field '{}' fetch returned '{}'", executionId, path, fetchedValueRaw == null ? "null" : fetchedValueRaw.getClass().getName()); - } - handleFetchedValue(fetchedValueRaw, result); - } catch (Exception e) { - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("'{}', field '{}' fetch threw exception", executionId, path, e); - } - result.completeExceptionally(e); - } - return result; - } - - private void handleFetchedValue(Object fetchedValue, CompletableFuture cf) { - if (fetchedValue == null) { - cf.complete(NULL_VALUE); - return; - } - if (fetchedValue instanceof CompletionStage) { - //noinspection unchecked - CompletionStage stage = (CompletionStage) fetchedValue; - stage.whenComplete((value, throwable) -> { - if (throwable != null) { - cf.completeExceptionally(throwable); - } else { - cf.complete(value); - } - }); - return; - } - cf.complete(fetchedValue); - } - - private FetchedValue unboxPossibleDataFetcherResult(MergedField sameField, ResultPath resultPath, FetchedValue result, Object localContext) { - if (result.getFetchedValue() instanceof DataFetcherResult) { - - DataFetcherResult dataFetcherResult = (DataFetcherResult) result.getFetchedValue(); - List addErrors = ImmutableList.copyOf(dataFetcherResult.getErrors()); - List newErrors = ImmutableKit.concatLists(result.getErrors(), addErrors); - - Object newLocalContext = dataFetcherResult.getLocalContext(); - if (newLocalContext == null) { - // if the field returns nothing then they get the context of their parent field - newLocalContext = localContext; - } - return FetchedValue.newFetchedValue() - .fetchedValue(dataFetcherResult.getData()) - .rawFetchedValue(result.getRawFetchedValue()) - .errors(newErrors) - .localContext(newLocalContext) - .build(); - } else { - return result; - } - } -} diff --git a/src/main/java/graphql/execution/nextgen/package-info.java b/src/main/java/graphql/execution/nextgen/package-info.java deleted file mode 100644 index 703901ee64..0000000000 --- a/src/main/java/graphql/execution/nextgen/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * WARNING: All code in this package is a work in progress for a new execution engine. - * It is not really "wired up" and can't be really used and should not be used yet! - * - * Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Internal -@Deprecated -package graphql.execution.nextgen; - -import graphql.Internal; \ No newline at end of file diff --git a/src/main/java/graphql/execution/nextgen/result/ExecutionResultNode.java b/src/main/java/graphql/execution/nextgen/result/ExecutionResultNode.java deleted file mode 100644 index 907112a707..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ExecutionResultNode.java +++ /dev/null @@ -1,115 +0,0 @@ -package graphql.execution.nextgen.result; - -import com.google.common.collect.ImmutableList; -import graphql.Assert; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.MergedField; -import graphql.execution.NonNullableFieldWasNullException; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static graphql.Assert.assertNotNull; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public abstract class ExecutionResultNode { - - private final ExecutionStepInfo executionStepInfo; - private final ResolvedValue resolvedValue; - private final NonNullableFieldWasNullException nonNullableFieldWasNullException; - private final ImmutableList children; - private final ImmutableList errors; - - /* - * we are trusting here the the children list is not modified on the outside (no defensive copy) - */ - protected ExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - NonNullableFieldWasNullException nonNullableFieldWasNullException, - List children, - List errors) { - this.resolvedValue = resolvedValue; - this.executionStepInfo = executionStepInfo; - this.nonNullableFieldWasNullException = nonNullableFieldWasNullException; - this.children = ImmutableList.copyOf(assertNotNull(children)); - children.forEach(Assert::assertNotNull); - this.errors = ImmutableList.copyOf(errors); - } - - public List getErrors() { - return new ArrayList<>(errors); - } - - /* - * can be null for the RootExecutionResultNode - */ - public ResolvedValue getResolvedValue() { - return resolvedValue; - } - - public MergedField getMergedField() { - return executionStepInfo.getField(); - } - - public ExecutionStepInfo getExecutionStepInfo() { - return executionStepInfo; - } - - public NonNullableFieldWasNullException getNonNullableFieldWasNullException() { - return nonNullableFieldWasNullException; - } - - public List getChildren() { - return this.children; - } - - public Optional getChildNonNullableException() { - return children.stream() - .filter(executionResultNode -> executionResultNode.getNonNullableFieldWasNullException() != null) - .map(ExecutionResultNode::getNonNullableFieldWasNullException) - .findFirst(); - } - - /** - * Creates a new ExecutionResultNode of the same specific type with the new set of result children - * - * @param children the new children for this result node - * - * @return a new ExecutionResultNode with the new result children - */ - public abstract ExecutionResultNode withNewChildren(List children); - - public abstract ExecutionResultNode withNewResolvedValue(ResolvedValue resolvedValue); - - public abstract ExecutionResultNode withNewExecutionStepInfo(ExecutionStepInfo executionStepInfo); - - - /** - * Creates a new ExecutionResultNode of the same specific type with the new error collection - * - * @param errors the new errors for this result node - * - * @return a new ExecutionResultNode with the new errors - */ - public abstract ExecutionResultNode withNewErrors(List errors); - - - @Override - public String toString() { - return "ExecutionResultNode{" + - "executionStepInfo=" + executionStepInfo + - ", resolvedValue=" + resolvedValue + - ", nonNullableFieldWasNullException=" + nonNullableFieldWasNullException + - ", children=" + children + - ", errors=" + errors + - '}'; - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/LeafExecutionResultNode.java b/src/main/java/graphql/execution/nextgen/result/LeafExecutionResultNode.java deleted file mode 100644 index 01ad7b08bb..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/LeafExecutionResultNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Assert; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.NonNullableFieldWasNullException; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class LeafExecutionResultNode extends ExecutionResultNode { - - public LeafExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - NonNullableFieldWasNullException nonNullableFieldWasNullException) { - this(executionStepInfo, resolvedValue, nonNullableFieldWasNullException, ImmutableKit.emptyList()); - } - - public LeafExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - NonNullableFieldWasNullException nonNullableFieldWasNullException, - List errors) { - super(executionStepInfo, resolvedValue, nonNullableFieldWasNullException, ImmutableKit.emptyList(), errors); - } - - - public Object getValue() { - return getResolvedValue().getCompletedValue(); - } - - @Override - public ExecutionResultNode withNewChildren(List children) { - return Assert.assertShouldNeverHappen(); - } - - @Override - public ExecutionResultNode withNewExecutionStepInfo(ExecutionStepInfo executionStepInfo) { - return new LeafExecutionResultNode(executionStepInfo, getResolvedValue(), getNonNullableFieldWasNullException(), getErrors()); - } - - @Override - public ExecutionResultNode withNewResolvedValue(ResolvedValue resolvedValue) { - return new LeafExecutionResultNode(getExecutionStepInfo(), resolvedValue, getNonNullableFieldWasNullException(), getErrors()); - } - - @Override - public ExecutionResultNode withNewErrors(List errors) { - return new LeafExecutionResultNode(getExecutionStepInfo(), getResolvedValue(), getNonNullableFieldWasNullException(), new ArrayList<>(errors)); - } -} \ No newline at end of file diff --git a/src/main/java/graphql/execution/nextgen/result/ListExecutionResultNode.java b/src/main/java/graphql/execution/nextgen/result/ListExecutionResultNode.java deleted file mode 100644 index feee93e321..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ListExecutionResultNode.java +++ /dev/null @@ -1,51 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.ExecutionStepInfo; - -import java.util.ArrayList; -import java.util.List; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ListExecutionResultNode extends ExecutionResultNode { - - public ListExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - List children) { - this(executionStepInfo, resolvedValue, children, ImmutableKit.emptyList()); - - } - - public ListExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - List children, - List errors) { - super(executionStepInfo, resolvedValue, ResultNodesUtil.newNullableException(executionStepInfo, children), children, errors); - } - - @Override - public ExecutionResultNode withNewChildren(List children) { - return new ListExecutionResultNode(getExecutionStepInfo(), getResolvedValue(), children, getErrors()); - } - - @Override - public ExecutionResultNode withNewResolvedValue(ResolvedValue resolvedValue) { - return new ListExecutionResultNode(getExecutionStepInfo(), resolvedValue, getChildren(), getErrors()); - } - - @Override - public ExecutionResultNode withNewExecutionStepInfo(ExecutionStepInfo executionStepInfo) { - return new ListExecutionResultNode(executionStepInfo, getResolvedValue(), getChildren(), getErrors()); - } - - @Override - public ExecutionResultNode withNewErrors(List errors) { - return new ListExecutionResultNode(getExecutionStepInfo(), getResolvedValue(), getChildren(), new ArrayList<>(errors)); - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/NamedResultNode.java b/src/main/java/graphql/execution/nextgen/result/NamedResultNode.java deleted file mode 100644 index 36aedc24e9..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/NamedResultNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Internal; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class NamedResultNode { - private final String name; - private final ExecutionResultNode node; - - public NamedResultNode(String name, ExecutionResultNode node) { - this.name = name; - this.node = node; - } - - public String getName() { - return name; - } - - public ExecutionResultNode getNode() { - return node; - } - - public NamedResultNode withNode(ExecutionResultNode newNode) { - return new NamedResultNode(name, newNode); - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/ObjectExecutionResultNode.java b/src/main/java/graphql/execution/nextgen/result/ObjectExecutionResultNode.java deleted file mode 100644 index f328c05b7b..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ObjectExecutionResultNode.java +++ /dev/null @@ -1,53 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.ExecutionStepInfo; - -import java.util.ArrayList; -import java.util.List; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ObjectExecutionResultNode extends ExecutionResultNode { - - - public ObjectExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - List children) { - this(executionStepInfo, resolvedValue, children, ImmutableKit.emptyList()); - - } - - public ObjectExecutionResultNode(ExecutionStepInfo executionStepInfo, - ResolvedValue resolvedValue, - List children, - List errors) { - super(executionStepInfo, resolvedValue, ResultNodesUtil.newNullableException(executionStepInfo, children), children, errors); - } - - - @Override - public ObjectExecutionResultNode withNewChildren(List children) { - return new ObjectExecutionResultNode(getExecutionStepInfo(), getResolvedValue(), children, getErrors()); - } - - @Override - public ExecutionResultNode withNewResolvedValue(ResolvedValue resolvedValue) { - return new ObjectExecutionResultNode(getExecutionStepInfo(), resolvedValue, getChildren(), getErrors()); - } - - @Override - public ExecutionResultNode withNewExecutionStepInfo(ExecutionStepInfo executionStepInfo) { - return new ObjectExecutionResultNode(executionStepInfo, getResolvedValue(), getChildren(), getErrors()); - } - - @Override - public ExecutionResultNode withNewErrors(List errors) { - return new ObjectExecutionResultNode(getExecutionStepInfo(), getResolvedValue(), getChildren(), new ArrayList<>(errors)); - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/ResolvedValue.java b/src/main/java/graphql/execution/nextgen/result/ResolvedValue.java deleted file mode 100644 index 4fc78b302d..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ResolvedValue.java +++ /dev/null @@ -1,101 +0,0 @@ -package graphql.execution.nextgen.result; - -import com.google.common.collect.ImmutableList; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ResolvedValue { - - private final Object completedValue; - private final Object localContext; - private final boolean nullValue; - private final ImmutableList errors; - - private ResolvedValue(Builder builder) { - this.completedValue = builder.completedValue; - this.localContext = builder.localContext; - this.nullValue = builder.nullValue; - this.errors = ImmutableList.copyOf(builder.errors); - } - - public Object getCompletedValue() { - return completedValue; - } - - public Object getLocalContext() { - return localContext; - } - - public boolean isNullValue() { - return nullValue; - } - - public List getErrors() { - return errors; - } - - public static Builder newResolvedValue() { - return new Builder(); - } - - - public ResolvedValue transform(Consumer builderConsumer) { - Builder builder = new Builder(this); - builderConsumer.accept(builder); - return builder.build(); - } - - - public static class Builder { - private Object completedValue; - private Object localContext; - private boolean nullValue; - private List errors = ImmutableKit.emptyList(); - - private Builder() { - - } - - private Builder(ResolvedValue existing) { - this.completedValue = existing.completedValue; - this.localContext = existing.localContext; - this.nullValue = existing.nullValue; - this.errors = existing.errors; - } - - public Builder completedValue(Object completedValue) { - this.completedValue = completedValue; - return this; - } - - public Builder localContext(Object localContext) { - this.localContext = localContext; - return this; - } - - public Builder nullValue(boolean nullValue) { - this.nullValue = nullValue; - return this; - } - - public Builder errors(List errors) { - this.errors = new ArrayList<>(errors); - return this; - } - - public ResolvedValue build() { - return new ResolvedValue(this); - } - } - -} diff --git a/src/main/java/graphql/execution/nextgen/result/ResultNodeAdapter.java b/src/main/java/graphql/execution/nextgen/result/ResultNodeAdapter.java deleted file mode 100644 index ccbe0910bb..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ResultNodeAdapter.java +++ /dev/null @@ -1,49 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Internal; -import graphql.util.NodeAdapter; -import graphql.util.NodeLocation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static graphql.Assert.assertNotNull; -import static graphql.Assert.assertTrue; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ResultNodeAdapter implements NodeAdapter { - - public static final ResultNodeAdapter RESULT_NODE_ADAPTER = new ResultNodeAdapter(); - - private ResultNodeAdapter() { - - } - - @Override - public Map> getNamedChildren(ExecutionResultNode parentNode) { - return Collections.singletonMap(null, parentNode.getChildren()); - } - - @Override - public ExecutionResultNode withNewChildren(ExecutionResultNode parentNode, Map> newChildren) { - assertTrue(newChildren.size() == 1); - List childrenList = newChildren.get(null); - assertNotNull(childrenList); - return parentNode.withNewChildren(childrenList); - } - - @Override - public ExecutionResultNode removeChild(ExecutionResultNode parentNode, NodeLocation location) { - int index = location.getIndex(); - List childrenList = new ArrayList<>(parentNode.getChildren()); - assertTrue(index >= 0 && index < childrenList.size(), () -> "The remove index MUST be within the range of the children"); - childrenList.remove(index); - return parentNode.withNewChildren(childrenList); - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/ResultNodeTraverser.java b/src/main/java/graphql/execution/nextgen/result/ResultNodeTraverser.java deleted file mode 100644 index c988afb32e..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ResultNodeTraverser.java +++ /dev/null @@ -1,34 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Internal; -import graphql.util.Traverser; -import graphql.util.TraverserVisitor; - -import java.util.Collection; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ResultNodeTraverser { - - private final Traverser traverser; - - private ResultNodeTraverser(Traverser traverser) { - this.traverser = traverser; - } - - public static ResultNodeTraverser depthFirst() { - return new ResultNodeTraverser(Traverser.depthFirst(ExecutionResultNode::getChildren, null, null)); - } - - public void traverse(TraverserVisitor visitor, ExecutionResultNode root) { - traverser.traverse(root, visitor); - } - - public void traverse(TraverserVisitor visitor, Collection roots) { - traverser.traverse(roots, visitor); - } - -} diff --git a/src/main/java/graphql/execution/nextgen/result/ResultNodesUtil.java b/src/main/java/graphql/execution/nextgen/result/ResultNodesUtil.java deleted file mode 100644 index 77005ee806..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/ResultNodesUtil.java +++ /dev/null @@ -1,187 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Assert; -import graphql.ExecutionResult; -import graphql.ExecutionResultImpl; -import graphql.GraphQLError; -import graphql.Internal; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.NonNullableFieldWasNullError; -import graphql.execution.NonNullableFieldWasNullException; -import graphql.util.NodeLocation; -import graphql.util.NodeMultiZipper; -import graphql.util.NodeZipper; -import graphql.util.TraversalControl; -import graphql.util.TraverserContext; -import graphql.util.TraverserVisitorStub; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static graphql.collect.ImmutableKit.map; -import static graphql.execution.nextgen.result.ResultNodeAdapter.RESULT_NODE_ADAPTER; -import static graphql.collect.ImmutableKit.emptyList; -import static java.util.Collections.singleton; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class ResultNodesUtil { - - public static ExecutionResult toExecutionResult(RootExecutionResultNode root) { - ExecutionResultData executionResultData = toDataImpl(root); - return ExecutionResultImpl.newExecutionResult() - .data(executionResultData.data) - .errors(executionResultData.errors) - .build(); - } - - private static class ExecutionResultData { - Object data; - List errors; - - - public ExecutionResultData(Object data, List errors) { - this.data = data; - this.errors = errors; - } - } - - - private static ExecutionResultData data(Object data, ExecutionResultNode executionResultNode) { - List allErrors = new ArrayList<>(); - allErrors.addAll(executionResultNode.getResolvedValue().getErrors()); - allErrors.addAll(executionResultNode.getErrors()); - return new ExecutionResultData(data, allErrors); - } - - private static ExecutionResultData data(Object data, List errors) { - return new ExecutionResultData(data, errors); - } - - private static ExecutionResultData data(Object data, NonNullableFieldWasNullException exception) { - return new ExecutionResultData(data, Arrays.asList(new NonNullableFieldWasNullError(exception))); - } - - private static ExecutionResultData toDataImpl(ExecutionResultNode root) { - if (root instanceof LeafExecutionResultNode) { - return root.getResolvedValue().isNullValue() ? data(null, root) : data(((LeafExecutionResultNode) root).getValue(), root); - } - if (root instanceof ListExecutionResultNode) { - Optional childNonNullableException = root.getChildNonNullableException(); - if (childNonNullableException.isPresent()) { - return data(null, childNonNullableException.get()); - } - - List errors = new ArrayList<>(); - List data = new ArrayList<>(); - for (ExecutionResultNode child : root.getChildren()) { - ExecutionResultData erd = toDataImpl(child); - data.add(erd.data); - if (!erd.errors.isEmpty()) { - errors.addAll(erd.errors); - } - } - if (!root.getErrors().isEmpty()) { - errors.addAll(root.getErrors()); - } - return data(data, errors); - } - - if (root instanceof UnresolvedObjectResultNode) { - ExecutionStepInfo executionStepInfo = root.getExecutionStepInfo(); - return data("Not resolved : " + executionStepInfo.getPath() + " with field " + executionStepInfo.getField(), emptyList()); - } - if (root instanceof ObjectExecutionResultNode) { - Optional childrenNonNullableException = root.getChildNonNullableException(); - if (childrenNonNullableException.isPresent()) { - return data(null, childrenNonNullableException.get()); - } - Map resultMap = new LinkedHashMap<>(); - List errors = new ArrayList<>(); - root.getChildren().forEach(child -> { - ExecutionResultData executionResultData = toDataImpl(child); - resultMap.put(child.getMergedField().getResultKey(), executionResultData.data); - errors.addAll(executionResultData.errors); - }); - errors.addAll(root.getErrors()); - return data(resultMap, errors); - } - return Assert.assertShouldNeverHappen("An unexpected root type %s", root.getClass()); - } - - - public static Optional getFirstNonNullableException(Collection collection) { - return collection.stream() - .filter(executionResultNode -> executionResultNode.getNonNullableFieldWasNullException() != null) - .map(ExecutionResultNode::getNonNullableFieldWasNullException) - .findFirst(); - } - - public static NonNullableFieldWasNullException newNullableException(ExecutionStepInfo executionStepInfo, List children) { - return newNullableException(executionStepInfo, map(children, NamedResultNode::getNode)); - } - - public static Map namedNodesToMap(List namedResultNodes) { - Map result = new LinkedHashMap<>(); - for (NamedResultNode namedResultNode : namedResultNodes) { - result.put(namedResultNode.getName(), namedResultNode.getNode()); - } - return result; - } - - public static NonNullableFieldWasNullException newNullableException(ExecutionStepInfo executionStepInfo, Collection children) { - // can only happen for the root node - if (executionStepInfo == null) { - return null; - } - Assert.assertNotNull(children); - boolean listIsNonNull = executionStepInfo.isNonNullType(); - if (listIsNonNull) { - Optional firstNonNullableException = getFirstNonNullableException(children); - if (firstNonNullableException.isPresent()) { - return new NonNullableFieldWasNullException(firstNonNullableException.get()); - } - } - return null; - } - - public static List> getUnresolvedNodes(Collection roots) { - List> result = new ArrayList<>(); - - ResultNodeTraverser traverser = ResultNodeTraverser.depthFirst(); - traverser.traverse(new TraverserVisitorStub() { - @Override - public TraversalControl enter(TraverserContext context) { - if (context.thisNode() instanceof UnresolvedObjectResultNode) { - result.add(new NodeZipper<>(context.thisNode(), context.getBreadcrumbs(), RESULT_NODE_ADAPTER)); - } - return TraversalControl.CONTINUE; - } - - }, roots); - return result; - } - - public static NodeMultiZipper getUnresolvedNodes(ExecutionResultNode root) { - List> unresolvedNodes = getUnresolvedNodes(singleton(root)); - return new NodeMultiZipper<>(root, unresolvedNodes, RESULT_NODE_ADAPTER); - } - - - public static NodeLocation key(String name) { - return new NodeLocation(name, 0); - } - - public static NodeLocation index(int index) { - return new NodeLocation(null, index); - } - -} diff --git a/src/main/java/graphql/execution/nextgen/result/RootExecutionResultNode.java b/src/main/java/graphql/execution/nextgen/result/RootExecutionResultNode.java deleted file mode 100644 index b2f671c8a0..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/RootExecutionResultNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.GraphQLError; -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.ExecutionStepInfo; - -import java.util.ArrayList; -import java.util.List; - -import static graphql.Assert.assertShouldNeverHappen; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class RootExecutionResultNode extends ObjectExecutionResultNode { - - - public RootExecutionResultNode(List children, List errors) { - super(null, null, children, errors); - } - - public RootExecutionResultNode(List children) { - super(null, null, children, ImmutableKit.emptyList()); - } - - @Override - public ExecutionStepInfo getExecutionStepInfo() { - return assertShouldNeverHappen("not supported at root node"); - } - - @Override - public ResolvedValue getResolvedValue() { - return assertShouldNeverHappen("not supported at root node"); - } - - @Override - public RootExecutionResultNode withNewChildren(List children) { - return new RootExecutionResultNode(children, getErrors()); - } - - @Override - public ExecutionResultNode withNewResolvedValue(ResolvedValue resolvedValue) { - return assertShouldNeverHappen("not supported at root node"); - } - - @Override - public ExecutionResultNode withNewExecutionStepInfo(ExecutionStepInfo executionStepInfo) { - return assertShouldNeverHappen("not supported at root node"); - } - - @Override - public ExecutionResultNode withNewErrors(List errors) { - return new RootExecutionResultNode(getChildren(), new ArrayList<>(errors)); - } -} diff --git a/src/main/java/graphql/execution/nextgen/result/UnresolvedObjectResultNode.java b/src/main/java/graphql/execution/nextgen/result/UnresolvedObjectResultNode.java deleted file mode 100644 index f1b5f22c7b..0000000000 --- a/src/main/java/graphql/execution/nextgen/result/UnresolvedObjectResultNode.java +++ /dev/null @@ -1,18 +0,0 @@ -package graphql.execution.nextgen.result; - -import graphql.Internal; -import graphql.collect.ImmutableKit; -import graphql.execution.ExecutionStepInfo; - -/** - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@Internal -public class UnresolvedObjectResultNode extends ObjectExecutionResultNode { - - public UnresolvedObjectResultNode(ExecutionStepInfo executionStepInfo, ResolvedValue resolvedValue) { - super(executionStepInfo, resolvedValue, ImmutableKit.emptyList(), ImmutableKit.emptyList()); - } - -} \ No newline at end of file diff --git a/src/main/java/graphql/nextgen/GraphQL.java b/src/main/java/graphql/nextgen/GraphQL.java deleted file mode 100644 index dc65416124..0000000000 --- a/src/main/java/graphql/nextgen/GraphQL.java +++ /dev/null @@ -1,380 +0,0 @@ -package graphql.nextgen; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.ExecutionResultImpl; -import graphql.Internal; -import graphql.ParseAndValidate; -import graphql.ParseAndValidateResult; -import graphql.execution.AbortExecutionException; -import graphql.execution.ExecutionId; -import graphql.execution.ExecutionIdProvider; -import graphql.execution.instrumentation.DocumentAndVariables; -import graphql.execution.instrumentation.InstrumentationContext; -import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.nextgen.Instrumentation; -import graphql.execution.instrumentation.nextgen.InstrumentationCreateStateParameters; -import graphql.execution.instrumentation.nextgen.InstrumentationExecutionParameters; -import graphql.execution.instrumentation.nextgen.InstrumentationValidationParameters; -import graphql.execution.nextgen.DefaultExecutionStrategy; -import graphql.execution.nextgen.Execution; -import graphql.execution.nextgen.ExecutionStrategy; -import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; -import graphql.execution.preparsed.PreparsedDocumentEntry; -import graphql.execution.preparsed.PreparsedDocumentProvider; -import graphql.language.Document; -import graphql.schema.GraphQLSchema; -import graphql.util.LogKit; -import graphql.validation.ValidationError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; - -import static graphql.Assert.assertNotNull; - -/** - * - * @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Deprecated -@SuppressWarnings("Duplicates") -@Internal -public class GraphQL { - private static final Logger log = LoggerFactory.getLogger(graphql.GraphQL.class); - private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(ExecutionStrategy.class); - - private final GraphQLSchema graphQLSchema; - private final ExecutionStrategy executionStrategy; - private final ExecutionIdProvider idProvider; - private final Instrumentation instrumentation; - private final PreparsedDocumentProvider preparsedDocumentProvider; - - public GraphQL(Builder builder) { - this.graphQLSchema = builder.graphQLSchema; - this.executionStrategy = builder.executionStrategy; - this.idProvider = builder.idProvider; - this.preparsedDocumentProvider = builder.preparsedDocumentProvider; - this.instrumentation = builder.instrumentation; - } - - /** - * Executes the graphql query using the provided input object builder - *

- * This will return a completed {@link ExecutionResult} - * which is the result of executing the provided query. - * - * @param executionInputBuilder {@link ExecutionInput.Builder} - * - * @return an {@link ExecutionResult} which can include errors - */ - public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) { - return executeAsync(executionInputBuilder.build()).join(); - } - - /** - * Executes the graphql query using the provided input object builder - *

- * This will return a completed {@link ExecutionResult} - * which is the result of executing the provided query. - *

- * This allows a lambda style like : - *

-     * {@code
-     *    ExecutionResult result = graphql.execute(input -> input.query("{hello}").root(startingObj).context(contextObj));
-     * }
-     * 
- * - * @param builderFunction a function that is given a {@link ExecutionInput.Builder} - * - * @return a promise to an {@link ExecutionResult} which can include errors - */ - public CompletableFuture execute(UnaryOperator builderFunction) { - return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build()); - } - - /** - * Executes the graphql query using the provided input object - *

- * This will return a completed {@link ExecutionResult} - * which is the result of executing the provided query. - * - * @param executionInput {@link ExecutionInput} - * - * @return a promise to an {@link ExecutionResult} which can include errors - */ - public ExecutionResult execute(ExecutionInput executionInput) { - return executeAsync(executionInput).join(); - } - - /** - * Executes the graphql query using the provided input object builder - *

- * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} - * which is the result of executing the provided query. - * - * @param executionInputBuilder {@link ExecutionInput.Builder} - * - * @return a promise to an {@link ExecutionResult} which can include errors - */ - public CompletableFuture executeAsync(ExecutionInput.Builder executionInputBuilder) { - return executeAsync(executionInputBuilder.build()); - } - - /** - * Executes the graphql query using the provided input object builder - *

- * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} - * which is the result of executing the provided query. - *

- * This allows a lambda style like : - *

-     * {@code
-     *    ExecutionResult result = graphql.executeAsync(input -> input.query("{hello}").root(startingObj).context(contextObj));
-     * }
-     * 
- * - * @param builderFunction a function that is given a {@link ExecutionInput.Builder} - * - * @return a promise to an {@link ExecutionResult} which can include errors - */ - public CompletableFuture executeAsync(UnaryOperator builderFunction) { - return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build()); - } - - /** - * Executes the graphql query using the provided input object - *

- * This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult} - * which is the result of executing the provided query. - * - * @param executionInput {@link ExecutionInput} - * - * @return a promise to an {@link ExecutionResult} which can include errors - */ - public CompletableFuture executeAsync(ExecutionInput executionInput) { - try { - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); - } - - InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInput)); - - InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); - executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters); - - InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); - InstrumentationContext executionInstrumentation = instrumentation.beginExecution(instrumentationParameters); - - GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters); - - CompletableFuture executionResult = parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState); - // - // finish up instrumentation - executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted); - // - // allow instrumentation to tweak the result - executionResult = executionResult.thenApply(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters)); - return executionResult; - } catch (AbortExecutionException abortException) { - return CompletableFuture.completedFuture(abortException.toExecutionResult()); - } - } - - private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { - AtomicReference executionInputRef = new AtomicReference<>(executionInput); - Function computeFunction = transformedInput -> { - // if they change the original query in the pre-parser, then we want to see it downstream from then on - executionInputRef.set(transformedInput); - return parseAndValidate(executionInputRef, graphQLSchema, instrumentationState); - }; - CompletableFuture preparsedDoc = preparsedDocumentProvider.getDocumentAsync(executionInput, computeFunction); - return preparsedDoc.thenCompose(preparsedDocumentEntry -> { - if (preparsedDocumentEntry.hasErrors()) { - return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors())); - } - try { - return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState); - } catch (AbortExecutionException e) { - return CompletableFuture.completedFuture(e.toExecutionResult()); - } - }); - } - - private PreparsedDocumentEntry parseAndValidate(AtomicReference executionInputRef, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { - - ExecutionInput executionInput = executionInputRef.get(); - String query = executionInput.getQuery(); - - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("Parsing query: '{}'...", query); - } - ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState); - if (parseResult.isFailure()) { - logNotSafe.warn("Query did not parse : '{}'", executionInput.getQuery()); - return new PreparsedDocumentEntry(parseResult.getSyntaxException().toInvalidSyntaxError()); - } else { - final Document document = parseResult.getDocument(); - // they may have changed the document and the variables via instrumentation so update the reference to it - executionInput = executionInput.transform(builder -> builder.variables(parseResult.getVariables())); - executionInputRef.set(executionInput); - - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("Validating query: '{}'", query); - } - final List errors = validate(executionInput, document, graphQLSchema, instrumentationState); - if (!errors.isEmpty()) { - logNotSafe.warn("Query did not validate : '{}'", query); - return new PreparsedDocumentEntry(document, errors); - } - - return new PreparsedDocumentEntry(document); - } - } - - private ParseAndValidateResult parse(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { - InstrumentationExecutionParameters parameters = new InstrumentationExecutionParameters(executionInput, graphQLSchema, instrumentationState); - InstrumentationContext parseInstrumentation = instrumentation.beginParse(parameters); - CompletableFuture documentCF = new CompletableFuture<>(); - parseInstrumentation.onDispatched(documentCF); - - ParseAndValidateResult parseResult = ParseAndValidate.parse(executionInput); - if (parseResult.isFailure()) { - parseInstrumentation.onCompleted(null, parseResult.getSyntaxException()); - return parseResult; - } else { - documentCF.complete(parseResult.getDocument()); - parseInstrumentation.onCompleted(parseResult.getDocument(), null); - - DocumentAndVariables documentAndVariables = parseResult.getDocumentAndVariables(); - documentAndVariables = instrumentation.instrumentDocumentAndVariables(documentAndVariables, parameters); - return ParseAndValidateResult.newResult() - .document(documentAndVariables.getDocument()).variables(documentAndVariables.getVariables()).build(); - } - } - - private List validate(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { - InstrumentationContext> validationCtx = instrumentation.beginValidation(new InstrumentationValidationParameters(executionInput, document, graphQLSchema, instrumentationState)); - CompletableFuture> cf = new CompletableFuture<>(); - validationCtx.onDispatched(cf); - - Predicate> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true); - List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate, executionInput.getLocale()); - - validationCtx.onCompleted(validationErrors, null); - cf.complete(validationErrors); - return validationErrors; - } - - private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { - String query = executionInput.getQuery(); - String operationName = executionInput.getOperationName(); - Object context = executionInput.getGraphQLContext(); - - Execution execution = new Execution(); - ExecutionId executionId = idProvider.provide(query, operationName, context); - - if (logNotSafe.isDebugEnabled()) { - logNotSafe.debug("Executing '{}'. operation name: '{}'. query: '{}'. variables '{}'", executionId, executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); - } - CompletableFuture future = execution.execute(executionStrategy, document, graphQLSchema, executionId, executionInput, instrumentationState); - future = future.whenComplete((result, throwable) -> { - if (throwable != null) { - log.error(String.format("Execution '%s' threw exception when executing : query : '%s'. variables '%s'", executionId, executionInput.getQuery(), executionInput.getVariables()), throwable); - } else { - if (log.isDebugEnabled()) { - int errorCount = result.getErrors().size(); - if (errorCount > 0) { - log.debug("Execution '{}' completed with '{}' errors", executionId, errorCount); - } else { - log.debug("Execution '{}' completed with zero errors", executionId); - } - } - } - }); - return future; - } - - /** - * Helps you build a GraphQL object ready to execute queries - * - * @param graphQLSchema the schema to use - * - * @return a builder of GraphQL objects - */ - public static Builder newGraphQL(GraphQLSchema graphQLSchema) { - return new Builder(graphQLSchema); - } - - /** - * This helps you transform the current GraphQL object into another one by starting a builder with all - * the current values and allows you to transform it how you want. - * - * @param builderConsumer the consumer code that will be given a builder to transform - * - * @return a new GraphQL object based on calling build on that builder - */ - public GraphQL transform(Consumer builderConsumer) { - Builder builder = new Builder(this); - builderConsumer.accept(builder); - return builder.build(); - } - - - public static class Builder { - private GraphQLSchema graphQLSchema; - private ExecutionStrategy executionStrategy = new DefaultExecutionStrategy(); - private ExecutionIdProvider idProvider = ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER; - private Instrumentation instrumentation = new Instrumentation() { - }; - private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; - - - public Builder(GraphQLSchema graphQLSchema) { - this.graphQLSchema = graphQLSchema; - } - - public Builder(GraphQL graphQL) { - this.graphQLSchema = graphQL.graphQLSchema; - this.executionStrategy = graphQL.executionStrategy; - this.idProvider = graphQL.idProvider; - this.instrumentation = graphQL.instrumentation; - } - - public Builder schema(GraphQLSchema graphQLSchema) { - this.graphQLSchema = assertNotNull(graphQLSchema, () -> "GraphQLSchema must be non null"); - return this; - } - - public Builder executionStrategy(ExecutionStrategy executionStrategy) { - this.executionStrategy = assertNotNull(executionStrategy, () -> "ExecutionStrategy must be non null"); - return this; - } - - public Builder instrumentation(Instrumentation instrumentation) { - this.instrumentation = assertNotNull(instrumentation, () -> "Instrumentation must be non null"); - return this; - } - - public Builder preparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { - this.preparsedDocumentProvider = assertNotNull(preparsedDocumentProvider, () -> "PreparsedDocumentProvider must be non null"); - return this; - } - - public Builder executionIdProvider(ExecutionIdProvider executionIdProvider) { - this.idProvider = assertNotNull(executionIdProvider, () -> "ExecutionIdProvider must be non null"); - return this; - } - - public GraphQL build() { - assertNotNull(graphQLSchema, () -> "graphQLSchema must be non null"); - return new GraphQL(this); - } - } -} diff --git a/src/main/java/graphql/nextgen/package-info.java b/src/main/java/graphql/nextgen/package-info.java deleted file mode 100644 index 332290a018..0000000000 --- a/src/main/java/graphql/nextgen/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WARNING: All code in this package is a work in progress for a new execution engine. - * - *Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release. - */ -@Internal -@Deprecated -package graphql.nextgen; - -import graphql.Internal; \ No newline at end of file diff --git a/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy deleted file mode 100644 index 6ba86d013a..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/BatchedExecutionStrategyTest.groovy +++ /dev/null @@ -1,396 +0,0 @@ -package graphql.execution.nextgen - - -import graphql.nextgen.GraphQL -import graphql.schema.DataFetcher -import spock.lang.Specification - -import static graphql.ExecutionInput.newExecutionInput -import static graphql.TestUtil.schema - -class BatchedExecutionStrategyTest extends Specification { - - def "test simple execution"() { - def fooData = [id: "fooId", bar: [id: "barId", name: "someBar"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: Foo - } - type Foo { - id: ID - bar: Bar - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: fooData] - } - - def "test execution with lists"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], [id: "barId2", name: "someBar2"]]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: fooData] - } - - def "test execution with null element "() { - def fooData = [[id: "fooId1", bar: null], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - - then: - result.getData() == [foo: fooData] - - } - - def "test execution with null element in list"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: fooData] - } - - def "test execution with null element in non null list"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar!] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - def expectedFooData = [[id: "fooId1", bar: null], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: expectedFooData] - } - - def "test execution with null element bubbling up because of non null "() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar!]! - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - def expectedFooData = [null, - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: expectedFooData] - } - - def "test execution with null element bubbling up to top "() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo!]! - } - type Foo { - id: ID - bar: [Bar!]! - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == null - } - - def "test list"() { - def fooData = [[id: "fooId1"], [id: "fooId2"], [id: "fooId3"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - } - """, dataFetchers) - - - def query = """ - {foo { - id - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [foo: fooData] - } - - def "test list in lists "() { - def catsBatchSize = 0 - def catsCallCount = 0 - def idsBatchSize = 0 - def idsCallCount = 0 - - def catsDataFetcher = { env -> - catsCallCount++ - catsBatchSize = env.getSource().size() - return [["cat1", "cat2"], null, ["cat3", "cat4", "cat5"]] - } as BatchedDataFetcher - - def idDataFetcher = { env -> - idsCallCount++ - idsBatchSize = env.getSource().size() - return ["catId1", "catId2", "catId3", "catId4", "catId5"] - } as BatchedDataFetcher - - def friendsData = ["friend1", "friend2", "friend3"] - def dataFetchers = [ - Query : [friends: { env -> friendsData } as DataFetcher], - Person: [cats: catsDataFetcher], - Cat : [id: idDataFetcher] - ] - def schema = schema(""" - type Query { - friends: [Person] - } - type Person { - cats: [Cat] - } - type Cat { - id: ID - } - """, dataFetchers) - - - def query = """ - {friends { - cats { - id - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - then: - result.getData() == [friends: [[cats: [[id: "catId1"], [id: "catId2"]]], [cats: null], [cats: [[id: "catId3"], [id: "catId4"], [id: "catId5"]]]]] - catsCallCount == 1 - idsCallCount == 1 - catsBatchSize == 3 - idsBatchSize == 5 - } - - def "test simple batching with null value in list"() { - def fooData = [[id: "fooId1"], null, [id: "fooId3"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - } - """, dataFetchers) - - - def query = """ - {foo { - id - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new BatchedExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput(query)) - - then: - result.getData() == [foo: fooData] - } -} - diff --git a/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy deleted file mode 100644 index 24bd070530..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/DefaultExecutionStrategyTest.groovy +++ /dev/null @@ -1,549 +0,0 @@ -package graphql.execution.nextgen - -import graphql.ExceptionWhileDataFetching -import graphql.nextgen.GraphQL -import graphql.schema.DataFetcher -import graphql.schema.DataFetchingEnvironment -import spock.lang.Specification - -import java.util.concurrent.CompletableFuture - -import static graphql.ExecutionInput.newExecutionInput -import static graphql.TestUtil.schema -import static graphql.execution.DataFetcherResult.newResult - -class DefaultExecutionStrategyTest extends Specification { - - def "test simple execution with one scalar field"() { - def fooData = "hello" - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: String - } - """, dataFetchers) - - - def query = """ - {foo} - """ - - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - - } - - def "test simple execution"() { - def fooData = [id: "fooId", bar: [id: "barId", name: "someBar"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: Foo - } - type Foo { - id: ID - bar: Bar - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - - } - - @SuppressWarnings("GroovyAssignabilityCheck") - def "fields are resolved in depth in parallel"() { - - List cfs = [] - - def fooResolver = { env -> - println env.getField() - def result = new CompletableFuture() - cfs << result - result - } as DataFetcher - def idResolver1 = Mock(DataFetcher) - def idResolver2 = Mock(DataFetcher) - def idResolver3 = Mock(DataFetcher) - def dataFetchers = [ - Query: [foo: fooResolver], - Foo : [id1: idResolver1, id2: idResolver2, id3: idResolver3] - ] - def schema = schema(""" - type Query { - foo: Foo - } - type Foo { - id1: ID - id2: ID - id3: ID - } - """, dataFetchers) - - - def query = """ - { - f1: foo { id1 } - f2: foo { id2 } - f3: foo { id3 } - } - """ - - def cfId1 = new CompletableFuture() - def cfId2 = new CompletableFuture() - def cfId3 = new CompletableFuture() - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - // - // I think this is a dangerous test - It dispatches the query but never joins on the result - // so its expecting the DFs to be called by never resolved properly. ?? - graphQL.executeAsync(newExecutionInput().query(query)) - - then: - cfs.size() == 3 - 0 * idResolver1.get(_) - 0 * idResolver1.get(_) - 0 * idResolver1.get(_) - - when: - cfs[1].complete(new Object()) - then: - 0 * idResolver1.get(_) - 1 * idResolver2.get(_) >> cfId2 - 0 * idResolver3.get(_) - - when: - cfs[2].complete(new Object()) - then: - 0 * idResolver1.get(_) - 0 * idResolver2.get(_) - 1 * idResolver3.get(_) >> cfId3 - - - when: - cfs[0].complete(new Object()) - then: - 1 * idResolver1.get(_) >> cfId1 - 0 * idResolver2.get(_) - 0 * idResolver3.get(_) - - } - - def "test execution with lists"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], [id: "barId2", name: "someBar2"]]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - } - - def "test execution with null element "() { - def fooData = [[id: "fooId1", bar: null], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - - } - - def "test execution with null element in list"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - - } - - def "test execution with null element in non null list"() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar!] - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - def expectedFooData = [[id: "fooId1", bar: null], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - - - when: - def graphQL = GraphQL.newGraphQL(schema).executionStrategy(new DefaultExecutionStrategy()).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: expectedFooData] - - } - - def "test execution with null element bubbling up because of non null "() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - bar: [Bar!]! - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - def expectedFooData = [null, - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: expectedFooData] - } - - def "test execution with null element bubbling up to top "() { - def fooData = [[id: "fooId1", bar: [[id: "barId1", name: "someBar1"], null]], - [id: "fooId2", bar: [[id: "barId3", name: "someBar3"], [id: "barId4", name: "someBar4"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo!]! - } - type Foo { - id: ID - bar: [Bar!]! - } - type Bar { - id: ID - name: String - } - """, dataFetchers) - - - def query = """ - {foo { - id - bar { - id - name - } - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == null - result.getErrors().size() > 0 - } - - def "test list"() { - def fooData = [[id: "fooId1"], [id: "fooId2"], [id: "fooId3"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - } - """, dataFetchers) - - - def query = """ - {foo { - id - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - } - - def "test list in lists "() { - def fooData = [[bar: [[id: "barId1"], [id: "barId2"]]], [bar: null], [bar: [[id: "barId3"], [id: "barId4"], [id: "barId5"]]]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - bar: [Bar] - } - type Bar { - id: ID - } - """, dataFetchers) - - - def query = """ - {foo { - bar { - id - } - }} - """ - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - } - - def "test simple batching with null value in list"() { - def fooData = [[id: "fooId1"], null, [id: "fooId3"]] - def dataFetchers = [ - Query: [foo: { env -> fooData } as DataFetcher] - ] - def schema = schema(""" - type Query { - foo: [Foo] - } - type Foo { - id: ID - } - """, dataFetchers) - - - def query = """ - {foo { - id - }} - """ - - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.getData() == [foo: fooData] - } - - - def "DataFetcherResult is respected with errors"() { - - def fooData = [[id: "fooId1"], null, [id: "fooId3"]] - def dataFetchers = [ - Query: [ - foo: { env -> - newResult().data(fooData) - .error(mkError(env)) - .build() - } as DataFetcher], - Foo : [ - id: { env -> - def id = env.source[env.getField().getName()] - newResult().data(id) - .error(mkError(env)) - .build() - } as DataFetcher - ] - ] - def schema = schema(''' - type Query { - foo: [Foo] - } - type Foo { - id: ID - } - ''', dataFetchers) - - - def query = ''' - { - foo { - id - } - } - ''' - - when: - def graphQL = GraphQL.newGraphQL(schema).build() - def result = graphQL.execute(newExecutionInput().query(query)) - - then: - result.errors.size() == 3 - result.data == [foo: fooData] - } - - private static ExceptionWhileDataFetching mkError(DataFetchingEnvironment env) { - def rte = new RuntimeException("Bang on " + env.getField().getName()) - new ExceptionWhileDataFetching(env.executionStepInfo.getPath(), rte, env.getField().sourceLocation) - } -} diff --git a/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTest.groovy b/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTest.groovy deleted file mode 100644 index a2ad92b1a2..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTest.groovy +++ /dev/null @@ -1,90 +0,0 @@ -package graphql.execution.nextgen.result - -import graphql.Scalars -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -import static graphql.GraphqlErrorBuilder.newError -import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo - -class ExecutionResultNodeTest extends Specification { - - @Shared - def startingErrors = [newError().message("Starting").build()] - - @Shared - def startingExecutionStepInfo = newExecutionStepInfo().type(Scalars.GraphQLString).build() - @Shared - def startingResolveValue = ResolvedValue.newResolvedValue().completedValue("start").build(); - - @Shared - def startingChildren = [new LeafExecutionResultNode(startingExecutionStepInfo, startingResolveValue, null)] - - @Unroll - def "construction of objects with new errors works"() { - - given: - def expectedErrors = [newError().message("Expected").build()] - - expect: - ExecutionResultNode nodeUnderTest = node - def newNode = nodeUnderTest.withNewErrors(expectedErrors) - newNode != nodeUnderTest - newNode.getErrors() == expectedErrors - - where: - - node | _ - new RootExecutionResultNode([], startingErrors) | _ - new ObjectExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - new ListExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - new LeafExecutionResultNode(startingExecutionStepInfo, startingResolveValue, null, startingErrors) | _ - } - - @Unroll - def "construction of objects with new esi works"() { - - given: - def newEsi = newExecutionStepInfo().type(Scalars.GraphQLString).build() - - expect: - ExecutionResultNode nodeUnderTest = node - def newNode = nodeUnderTest.withNewExecutionStepInfo(newEsi) - newNode != nodeUnderTest - newNode.getExecutionStepInfo() == newEsi - - - where: - - node | _ - new ObjectExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - new ListExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - new LeafExecutionResultNode(startingExecutionStepInfo, startingResolveValue, null, startingErrors) | _ - } - - @Unroll - def "construction of objects with new children works"() { - - given: - def newChildren = [ - new LeafExecutionResultNode(startingExecutionStepInfo, startingResolveValue, null), - new LeafExecutionResultNode(startingExecutionStepInfo, startingResolveValue, null), - ] - - expect: - ExecutionResultNode nodeUnderTest = node - node.getChildren().size() == 1 - def newNode = nodeUnderTest.withNewChildren(newChildren) - newNode != nodeUnderTest - newNode.getChildren().size() == 2 - - - where: - - node | _ - new RootExecutionResultNode(startingChildren, startingErrors) | _ - new ObjectExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - new ListExecutionResultNode(startingExecutionStepInfo, startingResolveValue, startingChildren, startingErrors) | _ - } -} diff --git a/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTestUtils.groovy b/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTestUtils.groovy deleted file mode 100644 index 4aeac436dd..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/result/ExecutionResultNodeTestUtils.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package graphql.execution.nextgen.result - -import graphql.Scalars -import graphql.execution.ExecutionStepInfo - -import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo - -class ExecutionResultNodeTestUtils { - - - static ResolvedValue resolvedValue(Object value) { - return ResolvedValue.newResolvedValue().completedValue(value).build() - } - - static ExecutionStepInfo esi() { - return newExecutionStepInfo().type(Scalars.GraphQLString).build() - } - -} diff --git a/src/test/groovy/graphql/execution/nextgen/result/ResultNodeAdapterTest.groovy b/src/test/groovy/graphql/execution/nextgen/result/ResultNodeAdapterTest.groovy deleted file mode 100644 index bf0b3e3a1a..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/result/ResultNodeAdapterTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package graphql.execution.nextgen.result - -import graphql.AssertException -import graphql.util.NodeLocation -import spock.lang.Specification - -import static graphql.execution.nextgen.result.ExecutionResultNodeTestUtils.esi -import static graphql.execution.nextgen.result.ExecutionResultNodeTestUtils.resolvedValue - -class ResultNodeAdapterTest extends Specification { - - def parentNode = new ObjectExecutionResultNode(null, null, [ - new LeafExecutionResultNode(esi(), resolvedValue("v1"), null), - new LeafExecutionResultNode(esi(), resolvedValue("v2"), null), - new LeafExecutionResultNode(esi(), resolvedValue("v3"), null), - ]) - - def "can remove a child from a node"() { - when: - ResultNodeAdapter.RESULT_NODE_ADAPTER.removeChild(parentNode, new NodeLocation(null, -1)) - then: - thrown(AssertException) - - when: - ResultNodeAdapter.RESULT_NODE_ADAPTER.removeChild(parentNode, new NodeLocation(null, -3)) - then: - thrown(AssertException) - - when: - def newNode = ResultNodeAdapter.RESULT_NODE_ADAPTER.removeChild(parentNode, new NodeLocation(null, 1)) - then: - newNode.children.size() == 2 - newNode.children[0].getResolvedValue().getCompletedValue() == "v1" - newNode.children[1].getResolvedValue().getCompletedValue() == "v3" - } -} diff --git a/src/test/groovy/graphql/execution/nextgen/result/ResultNodesUtilTest.groovy b/src/test/groovy/graphql/execution/nextgen/result/ResultNodesUtilTest.groovy deleted file mode 100644 index 9f3042d02e..0000000000 --- a/src/test/groovy/graphql/execution/nextgen/result/ResultNodesUtilTest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package graphql.execution.nextgen.result - -import graphql.SerializationError -import graphql.execution.ExecutionStepInfo -import graphql.execution.MergedField -import graphql.execution.ResultPath -import graphql.schema.CoercingSerializeException -import spock.lang.Specification - -class ResultNodesUtilTest extends Specification { - - def "convert errors for null values"() { - given: - def error = new SerializationError(ResultPath.rootPath(), new CoercingSerializeException()) - ExecutionStepInfo executionStepInfo = Mock(ExecutionStepInfo) - MergedField mergedField = Mock(MergedField) - mergedField.getResultKey() >> "foo" - executionStepInfo.getField() >> mergedField - ResolvedValue resolvedValue = ResolvedValue.newResolvedValue() - .completedValue(null) - .nullValue(true) - .errors([error]) - .build() - - LeafExecutionResultNode leafExecutionResultNode = new LeafExecutionResultNode(executionStepInfo, resolvedValue, null) - ExecutionResultNode executionResultNode = new RootExecutionResultNode([leafExecutionResultNode]) - - when: - def executionResult = ResultNodesUtil.toExecutionResult(executionResultNode) - then: - executionResult.errors.size() == 1 - } -} diff --git a/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy b/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy deleted file mode 100644 index 5b59e6ca9c..0000000000 --- a/src/test/groovy/graphql/nextgen/GraphqlNextGenTest.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package graphql.nextgen - - -import graphql.schema.DataFetcher -import spock.lang.Specification - -import static graphql.ExecutionInput.newExecutionInput -import static graphql.TestUtil.schema - -class GraphqlNextGenTest extends Specification { - - def "simple query"() { - given: - def dataFetchers = [ - Query: [hello: { env -> "world" } as DataFetcher] - ] - - def schema = schema(''' - type Query { - hello : String! - } - ''', dataFetchers) - - def graphQL = GraphQL.newGraphQL(schema).build() - - when: - def result = graphQL.executeAsync(newExecutionInput('{ hello }')).get() - - then: - result.data == [hello: 'world'] - } -} From 05f4105fe5c4e652d5f1d3c973032acc267a1267 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 16 Aug 2022 18:59:30 +1000 Subject: [PATCH 078/294] Remove reference to nextgen Common util --- .../java/graphql/execution/Execution.java | 33 ++----------------- .../ExecutableNormalizedOperationFactory.java | 4 +-- .../java/graphql/schema/impl/SchemaUtil.java | 32 ++++++++++++++++++ 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index e0255f40f8..fa69bcbcce 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -18,6 +18,7 @@ import graphql.language.VariableDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; +import graphql.schema.impl.SchemaUtil; import graphql.util.LogKit; import org.slf4j.Logger; @@ -26,14 +27,10 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -import static graphql.Assert.assertShouldNeverHappen; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; import static graphql.execution.ExecutionStrategyParameters.newParameters; import static graphql.execution.instrumentation.SimpleInstrumentationContext.nonNullCtx; -import static graphql.language.OperationDefinition.Operation.MUTATION; -import static graphql.language.OperationDefinition.Operation.QUERY; -import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION; import static java.util.concurrent.CompletableFuture.completedFuture; @Internal @@ -115,7 +112,7 @@ private CompletableFuture executeOperation(ExecutionContext exe GraphQLObjectType operationRootType; try { - operationRootType = getOperationRootType(executionContext.getGraphQLSchema(), operationDefinition); + operationRootType = SchemaUtil.getOperationRootType(executionContext.getGraphQLSchema(), operationDefinition); } catch (RuntimeException rte) { if (rte instanceof GraphQLError) { ExecutionResult executionResult = new ExecutionResultImpl(Collections.singletonList((GraphQLError) rte)); @@ -175,30 +172,4 @@ private CompletableFuture executeOperation(ExecutionContext exe return result; } - - - private GraphQLObjectType getOperationRootType(GraphQLSchema graphQLSchema, OperationDefinition operationDefinition) { - OperationDefinition.Operation operation = operationDefinition.getOperation(); - if (operation == MUTATION) { - GraphQLObjectType mutationType = graphQLSchema.getMutationType(); - if (mutationType == null) { - throw new MissingRootTypeException("Schema is not configured for mutations.", operationDefinition.getSourceLocation()); - } - return mutationType; - } else if (operation == QUERY) { - GraphQLObjectType queryType = graphQLSchema.getQueryType(); - if (queryType == null) { - throw new MissingRootTypeException("Schema does not define the required query root type.", operationDefinition.getSourceLocation()); - } - return queryType; - } else if (operation == SUBSCRIPTION) { - GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType(); - if (subscriptionType == null) { - throw new MissingRootTypeException("Schema is not configured for subscriptions.", operationDefinition.getSourceLocation()); - } - return subscriptionType; - } else { - return assertShouldNeverHappen("Unhandled case. An extra operation enum has been added without code support"); - } - } } diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index 1dad8296f9..cdc46a2215 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -11,7 +11,6 @@ import graphql.execution.MergedField; import graphql.execution.RawVariables; import graphql.execution.ValuesResolver; -import graphql.execution.nextgen.Common; import graphql.introspection.Introspection; import graphql.language.Document; import graphql.language.Field; @@ -33,6 +32,7 @@ import graphql.schema.GraphQLTypeUtil; import graphql.schema.GraphQLUnionType; import graphql.schema.GraphQLUnmodifiedType; +import graphql.schema.impl.SchemaUtil; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -109,7 +109,7 @@ private ExecutableNormalizedOperation createNormalizedQueryImpl(GraphQLSchema gr .normalizedVariables(normalizedVariableValues) .build(); - GraphQLObjectType rootType = Common.getOperationRootType(graphQLSchema, operationDefinition); + GraphQLObjectType rootType = SchemaUtil.getOperationRootType(graphQLSchema, operationDefinition); CollectNFResult collectFromOperationResult = collectFromOperation(parameters, operationDefinition, rootType); diff --git a/src/main/java/graphql/schema/impl/SchemaUtil.java b/src/main/java/graphql/schema/impl/SchemaUtil.java index 1f66607938..a1f382d84d 100644 --- a/src/main/java/graphql/schema/impl/SchemaUtil.java +++ b/src/main/java/graphql/schema/impl/SchemaUtil.java @@ -3,6 +3,8 @@ import com.google.common.collect.ImmutableMap; import graphql.Internal; +import graphql.execution.MissingRootTypeException; +import graphql.language.OperationDefinition; import graphql.schema.GraphQLImplementingType; import graphql.schema.GraphQLNamedOutputType; import graphql.schema.GraphQLNamedType; @@ -21,6 +23,11 @@ import java.util.Map; import java.util.TreeMap; +import static graphql.Assert.assertShouldNeverHappen; +import static graphql.language.OperationDefinition.Operation.MUTATION; +import static graphql.language.OperationDefinition.Operation.QUERY; +import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION; + @Internal public class SchemaUtil { @@ -96,4 +103,29 @@ public static void replaceTypeReferences(GraphQLSchema schema) { SchemaTraverser schemaTraverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); schemaTraverser.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), roots); } + + public static GraphQLObjectType getOperationRootType(GraphQLSchema graphQLSchema, OperationDefinition operationDefinition) { + OperationDefinition.Operation operation = operationDefinition.getOperation(); + if (operation == MUTATION) { + GraphQLObjectType mutationType = graphQLSchema.getMutationType(); + if (mutationType == null) { + throw new MissingRootTypeException("Schema is not configured for mutations.", operationDefinition.getSourceLocation()); + } + return mutationType; + } else if (operation == QUERY) { + GraphQLObjectType queryType = graphQLSchema.getQueryType(); + if (queryType == null) { + throw new MissingRootTypeException("Schema does not define the required query root type.", operationDefinition.getSourceLocation()); + } + return queryType; + } else if (operation == SUBSCRIPTION) { + GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType(); + if (subscriptionType == null) { + throw new MissingRootTypeException("Schema is not configured for subscriptions.", operationDefinition.getSourceLocation()); + } + return subscriptionType; + } else { + return assertShouldNeverHappen("Unhandled case. An extra operation enum has been added without code support"); + } + } } From 1de20aea3304ed3ff8b276ce1a86d9fe81b67ad0 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 16 Aug 2022 20:23:21 +1000 Subject: [PATCH 079/294] Fix snapshot badge --- README.md | 2 +- README.zh_cn.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e58d92d2f9..f1b8e82cb4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a [GraphQL](https://github.com/graphql/graphql-spec) Java implementation [![Build](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml/badge.svg)](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml) [![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=19)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) -[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) +[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot&versionPrefix=0)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md) ### Documentation diff --git a/README.zh_cn.md b/README.zh_cn.md index 277d449449..28190754e6 100644 --- a/README.zh_cn.md +++ b/README.zh_cn.md @@ -6,7 +6,7 @@ [![Build](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml/badge.svg)](https://github.com/graphql-java/graphql-java/actions/workflows/master.yml) [![Latest Release](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?versionPrefix=19)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) -[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) +[![Latest Snapshot](https://img.shields.io/maven-central/v/com.graphql-java/graphql-java?label=maven-central%20snapshot&versionPrefix=0)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java/graphql-java/) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md) ### 文档 From 0c9a19e505943954feb9749e027fcfb023cba337 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:03:05 +1000 Subject: [PATCH 080/294] Fix up doco --- src/test/groovy/readme/ExecutionExamples.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/readme/ExecutionExamples.java b/src/test/groovy/readme/ExecutionExamples.java index ee3f3fa42a..0f8f01c7fe 100644 --- a/src/test/groovy/readme/ExecutionExamples.java +++ b/src/test/groovy/readme/ExecutionExamples.java @@ -15,6 +15,7 @@ import graphql.language.SourceLocation; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLSchema; @@ -164,21 +165,26 @@ private void blockedFields() { .addPattern("Droid.appearsIn") .addPattern(".*\\.hero") // it uses regular expressions .build(); + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(blockedFields) + .build(); GraphQLSchema schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(blockedFields) + .codeRegistry(codeRegistry) .build(); - //::/FigureJ } private void noIntrospection() { //::FigureK + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY) + .build(); GraphQLSchema schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY) + .codeRegistry(codeRegistry) .build(); //::/FigureK } From f3fde0e46391112293c282b2343480a0970b7394 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sat, 20 Aug 2022 14:01:18 +1000 Subject: [PATCH 081/294] We can rename scalar types (#2928) --- .../schema/SchemaTransformerTest.groovy | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 4d41f672b2..6cda5ee323 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -841,4 +841,33 @@ type Query { } ''' } + + def "can rename scalars"() { + + def schema = TestUtil.schema(""" + scalar Foo + type Query { + field : Foo + } +""") + + def visitor = new GraphQLTypeVisitorStub() { + + @Override + TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + if (node.getName().equals("Foo")) { + GraphQLScalarType newNode = node.transform({sc -> sc.name("Bar")}) + return changeNode(context, newNode) + } + return super.visitGraphQLScalarType(node, context) + } + } + + when: + def newSchema = SchemaTransformer.transformSchema(schema, visitor) + then: + newSchema.getType("Bar") instanceof GraphQLScalarType + newSchema.getType("Foo") == null + (newSchema.getObjectType("Query").getFieldDefinition("field").getType() as GraphQLScalarType).getName() == "Bar" + } } From f1cf7193f5ffba7fce57ff41bcd2c8be6b161720 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:04:23 +1000 Subject: [PATCH 082/294] Add @DeprecatedAt annotation --- src/main/java/graphql/DeprecatedAt.java | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/graphql/DeprecatedAt.java diff --git a/src/main/java/graphql/DeprecatedAt.java b/src/main/java/graphql/DeprecatedAt.java new file mode 100644 index 0000000000..0918e5f6d6 --- /dev/null +++ b/src/main/java/graphql/DeprecatedAt.java @@ -0,0 +1,22 @@ +package graphql; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; + +/** + * Helper to track deprecation + * + * Please use ISO-8601 format i.e. YYYY-MM-DD + */ +@Retention(RetentionPolicy.SOURCE) +@Target(value = {CONSTRUCTOR, METHOD, TYPE, FIELD, PACKAGE}) +public @interface DeprecatedAt { + String value(); +} From 47b64a18afc4786a951230764b75d6ea7cc44468 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:08:06 +1000 Subject: [PATCH 083/294] Add deprecated at dates --- src/main/java/graphql/DirectivesUtil.java | 7 +++++++ src/main/java/graphql/ExecutionInput.java | 7 +++++++ src/main/java/graphql/GraphQL.java | 4 ++++ .../graphql/TypeResolutionEnvironment.java | 1 + .../collect/ImmutableMapWithNullValues.java | 14 ++++++++++++++ .../execution/DataFetcherExceptionHandler.java | 2 ++ .../graphql/execution/ExecutionContext.java | 4 ++++ .../execution/ExecutionContextBuilder.java | 3 +++ .../graphql/execution/ExecutionStepInfo.java | 2 ++ .../execution/TypeResolutionParameters.java | 3 +++ .../instrumentation/Instrumentation.java | 18 ++++++++++++++++++ ...trumentationExecuteOperationParameters.java | 3 +++ .../InstrumentationExecutionParameters.java | 4 ++++ src/main/java/graphql/parser/Parser.java | 2 ++ .../schema/DataFetchingEnvironment.java | 4 ++++ .../DelegatingDataFetchingEnvironment.java | 4 ++++ .../java/graphql/schema/GraphQLArgument.java | 8 ++++++++ .../schema/GraphQLDirectiveContainer.java | 7 +++++++ .../java/graphql/schema/GraphQLSchema.java | 10 ++++++++++ .../idl/SchemaDirectiveWiringEnvironment.java | 7 +++++++ .../graphql/validation/ValidationError.java | 6 ++++++ 21 files changed, 120 insertions(+) diff --git a/src/main/java/graphql/DirectivesUtil.java b/src/main/java/graphql/DirectivesUtil.java index 1b0401562d..9a3e1fc3ac 100644 --- a/src/main/java/graphql/DirectivesUtil.java +++ b/src/main/java/graphql/DirectivesUtil.java @@ -25,6 +25,7 @@ public class DirectivesUtil { @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static Map nonRepeatableDirectivesByName(List directives) { // filter the repeatable directives List singletonDirectives = directives.stream() @@ -34,12 +35,14 @@ public static Map nonRepeatableDirectivesByName(List> allDirectivesByName(List directives) { return ImmutableMap.copyOf(FpKit.groupingBy(directives, GraphQLDirective::getName)); } @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static Optional directiveWithArg(List directives, String directiveName, String argumentName) { GraphQLDirective directive = nonRepeatableDirectivesByName(directives).get(directiveName); GraphQLArgument argument = null; @@ -51,6 +54,7 @@ public static Optional directiveWithArg(List @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static boolean isAllNonRepeatable(List directives) { if (directives == null || directives.isEmpty()) { return false; @@ -64,6 +68,7 @@ public static boolean isAllNonRepeatable(List directives) { } @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static List add(List targetList, GraphQLDirective newDirective) { assertNotNull(targetList, () -> "directive list can't be null"); assertNotNull(newDirective, () -> "directive can't be null"); @@ -72,6 +77,7 @@ public static List add(List targetList, Grap } @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static List addAll(List targetList, List newDirectives) { assertNotNull(targetList, () -> "directive list can't be null"); assertNotNull(newDirectives, () -> "directive list can't be null"); @@ -80,6 +86,7 @@ public static List addAll(List targetList, L } @Deprecated // use GraphQLAppliedDirectives eventually + @DeprecatedAt("2022-02-24") public static GraphQLDirective getFirstDirective(String name, Map> allDirectivesByName) { List directives = allDirectivesByName.getOrDefault(name, emptyList()); if (directives.isEmpty()) { diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 6d4ee0036b..376fa82249 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -72,6 +72,7 @@ public String getOperationName() { * @deprecated - use {@link #getGraphQLContext()} */ @Deprecated + @DeprecatedAt("2021-07-05") public Object getContext() { return context; } @@ -124,6 +125,7 @@ public DataLoaderRegistry getDataLoaderRegistry() { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl getCacheControl() { return cacheControl; } @@ -227,6 +229,7 @@ public static class Builder { // dataloader field tracking away. // private DataLoaderRegistry dataLoaderRegistry = DataLoaderDispatcherInstrumentationState.EMPTY_DATALOADER_REGISTRY; + @DeprecatedAt("2022-07-26") private CacheControl cacheControl = CacheControl.newCacheControl(); private Locale locale = Locale.getDefault(); private ExecutionId executionId; @@ -287,6 +290,7 @@ public Builder localContext(Object localContext) { * @deprecated - the {@link ExecutionInput#getGraphQLContext()} is a fixed mutable instance now */ @Deprecated + @DeprecatedAt("2021-07-05") public Builder context(Object context) { this.context = context; return this; @@ -302,6 +306,7 @@ public Builder context(Object context) { * @deprecated - the {@link ExecutionInput#getGraphQLContext()} is a fixed mutable instance now */ @Deprecated + @DeprecatedAt("2021-07-05") public Builder context(GraphQLContext.Builder contextBuilder) { this.context = contextBuilder.build(); return this; @@ -317,6 +322,7 @@ public Builder context(GraphQLContext.Builder contextBuilder) { * @deprecated - the {@link ExecutionInput#getGraphQLContext()} is a fixed mutable instance now */ @Deprecated + @DeprecatedAt("2021-07-05") public Builder context(UnaryOperator contextBuilderFunction) { GraphQLContext.Builder builder = GraphQLContext.newContext(); builder = contextBuilderFunction.apply(builder); @@ -394,6 +400,7 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) { } @Deprecated + @DeprecatedAt("2022-07-26") public Builder cacheControl(CacheControl cacheControl) { this.cacheControl = assertNotNull(cacheControl); return this; diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 0c9e5e70d3..4026ffc3c2 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -335,6 +335,7 @@ public ExecutionResult execute(String query) { * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated + @DeprecatedAt("2017-06-02") public ExecutionResult execute(String query, Object context) { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) @@ -356,6 +357,7 @@ public ExecutionResult execute(String query, Object context) { * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated + @DeprecatedAt("2017-06-02") public ExecutionResult execute(String query, String operationName, Object context) { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) @@ -378,6 +380,7 @@ public ExecutionResult execute(String query, String operationName, Object contex * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated + @DeprecatedAt("2017-06-02") public ExecutionResult execute(String query, Object context, Map variables) { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) @@ -401,6 +404,7 @@ public ExecutionResult execute(String query, Object context, Map * @deprecated Use {@link #execute(ExecutionInput)} */ @Deprecated + @DeprecatedAt("2017-06-02") public ExecutionResult execute(String query, String operationName, Object context, Map variables) { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) diff --git a/src/main/java/graphql/TypeResolutionEnvironment.java b/src/main/java/graphql/TypeResolutionEnvironment.java index 27edffe946..5e6a18fc86 100644 --- a/src/main/java/graphql/TypeResolutionEnvironment.java +++ b/src/main/java/graphql/TypeResolutionEnvironment.java @@ -96,6 +96,7 @@ public GraphQLSchema getSchema() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated + @DeprecatedAt("2021-12-27") public T getContext() { //noinspection unchecked return (T) context; diff --git a/src/main/java/graphql/collect/ImmutableMapWithNullValues.java b/src/main/java/graphql/collect/ImmutableMapWithNullValues.java index ed046098c3..207ad9b5d3 100644 --- a/src/main/java/graphql/collect/ImmutableMapWithNullValues.java +++ b/src/main/java/graphql/collect/ImmutableMapWithNullValues.java @@ -1,6 +1,7 @@ package graphql.collect; import graphql.Assert; +import graphql.DeprecatedAt; import graphql.Internal; import java.util.Collection; @@ -82,24 +83,28 @@ public V get(Object key) { @Override @Deprecated + @DeprecatedAt("2020-11-10") public V put(K key, V value) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V remove(Object key) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public void clear() { throw new UnsupportedOperationException(); } @@ -141,54 +146,63 @@ public void forEach(BiConsumer action) { @Override @Deprecated + @DeprecatedAt("2020-11-10") public void replaceAll(BiFunction function) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V putIfAbsent(K key, V value) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public boolean replace(K key, V oldValue, V newValue) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V replace(K key, V value) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V computeIfAbsent(K key, Function mappingFunction) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V computeIfPresent(K key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V compute(K key, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } @Override @Deprecated + @DeprecatedAt("2020-11-10") public V merge(K key, V value, BiFunction remappingFunction) { throw new UnsupportedOperationException(); } diff --git a/src/main/java/graphql/execution/DataFetcherExceptionHandler.java b/src/main/java/graphql/execution/DataFetcherExceptionHandler.java index 832b159429..cc355db12f 100644 --- a/src/main/java/graphql/execution/DataFetcherExceptionHandler.java +++ b/src/main/java/graphql/execution/DataFetcherExceptionHandler.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.DeprecatedAt; import graphql.ExecutionResult; import graphql.PublicSpi; import graphql.schema.DataFetcher; @@ -27,6 +28,7 @@ public interface DataFetcherExceptionHandler { * version */ @Deprecated + @DeprecatedAt("2021-06-23") default DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { return SimpleDataFetcherExceptionHandler.defaultImpl.onException(handlerParameters); } diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index d69c2b8672..26f4b575b7 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.GraphQLContext; import graphql.GraphQLError; @@ -121,6 +122,7 @@ public OperationDefinition getOperationDefinition() { * @deprecated use {@link #getCoercedVariables()} instead */ @Deprecated + @DeprecatedAt("2022-05-24") public Map getVariables() { return coercedVariables.toMap(); } @@ -136,6 +138,7 @@ public CoercedVariables getCoercedVariables() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated + @DeprecatedAt("2021-07-05") @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public T getContext() { return (T) context; @@ -164,6 +167,7 @@ public DataLoaderRegistry getDataLoaderRegistry() { } @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl getCacheControl() { return cacheControl; } diff --git a/src/main/java/graphql/execution/ExecutionContextBuilder.java b/src/main/java/graphql/execution/ExecutionContextBuilder.java index 6aaabf485c..898c3786eb 100644 --- a/src/main/java/graphql/execution/ExecutionContextBuilder.java +++ b/src/main/java/graphql/execution/ExecutionContextBuilder.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.GraphQLContext; import graphql.GraphQLError; @@ -157,6 +158,7 @@ public ExecutionContextBuilder root(Object root) { * @deprecated use {@link #coercedVariables(CoercedVariables)} instead */ @Deprecated + @DeprecatedAt("2022-05-24") public ExecutionContextBuilder variables(Map variables) { this.coercedVariables = CoercedVariables.of(variables); return this; @@ -188,6 +190,7 @@ public ExecutionContextBuilder dataLoaderRegistry(DataLoaderRegistry dataLoaderR } @Deprecated + @DeprecatedAt("2022-07-26") public ExecutionContextBuilder cacheControl(CacheControl cacheControl) { this.cacheControl = cacheControl; return this; diff --git a/src/main/java/graphql/execution/ExecutionStepInfo.java b/src/main/java/graphql/execution/ExecutionStepInfo.java index 60b5d6301c..884ca79179 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfo.java +++ b/src/main/java/graphql/execution/ExecutionStepInfo.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.collect.ImmutableMapWithNullValues; import graphql.schema.GraphQLFieldDefinition; @@ -84,6 +85,7 @@ private ExecutionStepInfo(Builder builder) { * @deprecated use {@link #getObjectType()} instead as it is named better */ @Deprecated + @DeprecatedAt("2022-02-03") public GraphQLObjectType getFieldContainer() { return fieldContainer; } diff --git a/src/main/java/graphql/execution/TypeResolutionParameters.java b/src/main/java/graphql/execution/TypeResolutionParameters.java index 0bb829cfb3..88a8e2db69 100644 --- a/src/main/java/graphql/execution/TypeResolutionParameters.java +++ b/src/main/java/graphql/execution/TypeResolutionParameters.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.DeprecatedAt; import graphql.GraphQLContext; import graphql.Internal; import graphql.TypeResolutionEnvironment; @@ -74,6 +75,7 @@ public static Builder newParameters() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated + @DeprecatedAt("2021-07-05") public Object getContext() { return context; } @@ -124,6 +126,7 @@ public Builder schema(GraphQLSchema schema) { } @Deprecated + @DeprecatedAt("2021-07-05") public Builder context(Object context) { this.context = context; return this; diff --git a/src/main/java/graphql/execution/instrumentation/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/Instrumentation.java index 1f19018284..11b41e48f5 100644 --- a/src/main/java/graphql/execution/instrumentation/Instrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/Instrumentation.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.PublicSpi; @@ -49,6 +50,7 @@ public interface Instrumentation { * @deprecated use {@link #createState(InstrumentationCreateStateParameters)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") default InstrumentationState createState() { return null; } @@ -76,6 +78,7 @@ default InstrumentationState createState(InstrumentationCreateStateParameters pa * @deprecated use {@link #beginExecution(InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { return noOp(); @@ -104,6 +107,7 @@ default InstrumentationContext beginExecution(InstrumentationEx * @deprecated use {@link #beginParse(InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { return noOp(); @@ -132,6 +136,7 @@ default InstrumentationContext beginParse(InstrumentationExecutionPara * @deprecated use {@link #beginValidation(InstrumentationValidationParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { return noOp(); @@ -160,6 +165,7 @@ default InstrumentationContext> beginValidation(Instrument * @deprecated use {@link #beginExecuteOperation(InstrumentationExecuteOperationParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { return noOp(); @@ -189,6 +195,7 @@ default InstrumentationContext beginExecuteOperation(Instrument * @deprecated use {@link #beginExecutionStrategy(InstrumentationExecutionStrategyParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { return ExecutionStrategyInstrumentationContext.NOOP; @@ -219,6 +226,7 @@ default ExecutionStrategyInstrumentationContext beginExecutionStrategy(Instrumen * @deprecated use {@link #beginSubscribedFieldEvent(InstrumentationFieldParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters) { return noOp(); @@ -247,6 +255,7 @@ default InstrumentationContext beginSubscribedFieldEvent(Instru * @deprecated use {@link #beginField(InstrumentationFieldParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginField(InstrumentationFieldParameters parameters) { return noOp(); @@ -275,6 +284,7 @@ default InstrumentationContext beginField(InstrumentationFieldP * @deprecated use {@link #beginFieldFetch(InstrumentationFieldFetchParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { return noOp(); @@ -304,6 +314,7 @@ default InstrumentationContext beginFieldFetch(InstrumentationFieldFetch * @deprecated use {@link #beginFieldComplete(InstrumentationFieldCompleteParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { return noOp(); @@ -332,6 +343,7 @@ default InstrumentationContext beginFieldComplete(Instrumentati * @deprecated use {@link #beginFieldListComplete(InstrumentationFieldCompleteParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) { return noOp(); @@ -362,6 +374,7 @@ default InstrumentationContext beginFieldListComplete(Instrumen * @deprecated use {@link #instrumentExecutionInput(ExecutionInput, InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { return executionInput; @@ -393,6 +406,7 @@ default ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, I * @deprecated use {@link #instrumentDocumentAndVariables(DocumentAndVariables, InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters) { return documentAndVariables; @@ -424,6 +438,7 @@ default DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables * @deprecated use {@link #instrumentSchema(GraphQLSchema, InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters) { return schema; @@ -456,6 +471,7 @@ default GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExec * @deprecated use {@link #instrumentExecutionContext(ExecutionContext, InstrumentationExecutionParameters)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters) { return executionContext; @@ -491,6 +507,7 @@ default ExecutionContext instrumentExecutionContext(ExecutionContext executionCo * @deprecated use {@link #instrumentDataFetcher(DataFetcher, InstrumentationFieldFetchParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { return dataFetcher; @@ -524,6 +541,7 @@ default DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, Instrum * @deprecated use {@link #instrumentExecutionResult(ExecutionResult, InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated + @DeprecatedAt("2022-07-26") @NotNull default CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { return CompletableFuture.completedFuture(executionResult); diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java index 5625dbeeee..f8a895657b 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.Instrumentation; @@ -33,6 +34,7 @@ private InstrumentationExecuteOperationParameters(ExecutionContext executionCont * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public InstrumentationExecuteOperationParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationExecuteOperationParameters(executionContext, instrumentationState); } @@ -52,6 +54,7 @@ public ExecutionContext getExecutionContext() { * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public T getInstrumentationState() { //noinspection unchecked return (T) instrumentationState; diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java index 6471af7965..57ec489851 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.GraphQLContext; import graphql.PublicApi; @@ -45,6 +46,7 @@ public InstrumentationExecutionParameters(ExecutionInput executionInput, GraphQL * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public InstrumentationExecutionParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationExecutionParameters(this.getExecutionInput(), this.schema, instrumentationState); } @@ -69,6 +71,7 @@ public String getOperation() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated + @DeprecatedAt("2021-07-05") @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public T getContext() { return (T) context; @@ -93,6 +96,7 @@ public Map getVariables() { * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @SuppressWarnings("TypeParameterUnusedInFormals") public T getInstrumentationState() { //noinspection unchecked diff --git a/src/main/java/graphql/parser/Parser.java b/src/main/java/graphql/parser/Parser.java index d2726dda68..edd0966aa6 100644 --- a/src/main/java/graphql/parser/Parser.java +++ b/src/main/java/graphql/parser/Parser.java @@ -1,5 +1,6 @@ package graphql.parser; +import graphql.DeprecatedAt; import graphql.Internal; import graphql.PublicApi; import graphql.language.Document; @@ -345,6 +346,7 @@ private void throwCancelParseIfTooManyTokens(Token token, int maxTokens, MultiSo * @deprecated - really should use {@link #getAntlrToLanguage(CommonTokenStream, MultiSourceReader, ParserOptions)} */ @Deprecated + @DeprecatedAt("2021-06-23") protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { return null; } diff --git a/src/main/java/graphql/schema/DataFetchingEnvironment.java b/src/main/java/graphql/schema/DataFetchingEnvironment.java index 153bee505f..578a234c36 100644 --- a/src/main/java/graphql/schema/DataFetchingEnvironment.java +++ b/src/main/java/graphql/schema/DataFetchingEnvironment.java @@ -1,5 +1,6 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.GraphQLContext; import graphql.PublicApi; import graphql.cachecontrol.CacheControl; @@ -88,6 +89,7 @@ public interface DataFetchingEnvironment extends IntrospectionDataFetchingEnviro * @deprecated - use {@link #getGraphQlContext()} instead */ @Deprecated + @DeprecatedAt("2021-07-05") T getContext(); /** @@ -138,6 +140,7 @@ public interface DataFetchingEnvironment extends IntrospectionDataFetchingEnviro * @deprecated Use {@link #getMergedField()}. */ @Deprecated + @DeprecatedAt("2018-12-20") List getFields(); /** @@ -242,6 +245,7 @@ public interface DataFetchingEnvironment extends IntrospectionDataFetchingEnviro * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") CacheControl getCacheControl(); /** diff --git a/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java b/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java index dac7e6e480..c2da68c397 100644 --- a/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java +++ b/src/main/java/graphql/schema/DelegatingDataFetchingEnvironment.java @@ -1,5 +1,6 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.GraphQLContext; import graphql.PublicApi; import graphql.cachecontrol.CacheControl; @@ -65,6 +66,7 @@ public T getArgumentOrDefault(String name, T defaultValue) { } @Deprecated + @DeprecatedAt("2022-04-17") @Override public T getContext() { return delegateEnvironment.getContext(); @@ -91,6 +93,7 @@ public GraphQLFieldDefinition getFieldDefinition() { } @Deprecated + @DeprecatedAt("2019-10-07") @Override public List getFields() { return delegateEnvironment.getFields(); @@ -163,6 +166,7 @@ public Locale getLocale() { @Override @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl getCacheControl() { return delegateEnvironment.getCacheControl(); } diff --git a/src/main/java/graphql/schema/GraphQLArgument.java b/src/main/java/graphql/schema/GraphQLArgument.java index d6aa2110d0..74c8477274 100644 --- a/src/main/java/graphql/schema/GraphQLArgument.java +++ b/src/main/java/graphql/schema/GraphQLArgument.java @@ -1,6 +1,7 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.DirectivesUtil; import graphql.PublicApi; import graphql.language.InputValueDefinition; @@ -123,6 +124,7 @@ public boolean hasSetValue() { * @deprecated use {@link GraphQLAppliedDirectiveArgument} instead */ @Deprecated + @DeprecatedAt("2022-02-24") public @NotNull InputValueWithState getArgumentValue() { return value; } @@ -146,6 +148,7 @@ public boolean hasSetValue() { * @deprecated use {@link GraphQLAppliedDirectiveArgument} instead */ @Deprecated + @DeprecatedAt("2022-02-24") public static T getArgumentValue(GraphQLArgument argument) { return getInputValueImpl(argument.getType(), argument.getArgumentValue()); } @@ -370,6 +373,7 @@ public Builder type(GraphQLInputType type) { * @deprecated use {@link #defaultValueLiteral(Value)} or {@link #defaultValueProgrammatic(Object)} */ @Deprecated + @DeprecatedAt("2021-05-10") public Builder defaultValue(Object defaultValue) { this.defaultValue = InputValueWithState.newInternalValue(defaultValue); return this; @@ -415,6 +419,7 @@ public Builder clearDefaultValue() { * @deprecated use {@link #valueLiteral(Value)} or {@link #valueProgrammatic(Object)} */ @Deprecated + @DeprecatedAt("2021-05-10") public Builder value(@Nullable Object value) { this.value = InputValueWithState.newInternalValue(value); return this; @@ -430,6 +435,7 @@ public Builder value(@Nullable Object value) { * @deprecated use {@link GraphQLAppliedDirectiveArgument} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public Builder valueLiteral(@NotNull Value value) { this.value = InputValueWithState.newLiteralValue(value); return this; @@ -443,6 +449,7 @@ public Builder valueLiteral(@NotNull Value value) { * @deprecated use {@link GraphQLAppliedDirectiveArgument} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public Builder valueProgrammatic(@Nullable Object value) { this.value = InputValueWithState.newExternalValue(value); return this; @@ -456,6 +463,7 @@ public Builder valueProgrammatic(@Nullable Object value) { * @deprecated use {@link GraphQLAppliedDirectiveArgument} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public Builder clearValue() { this.value = InputValueWithState.NOT_SET; return this; diff --git a/src/main/java/graphql/schema/GraphQLDirectiveContainer.java b/src/main/java/graphql/schema/GraphQLDirectiveContainer.java index 518562e3f1..c18d9a67b3 100644 --- a/src/main/java/graphql/schema/GraphQLDirectiveContainer.java +++ b/src/main/java/graphql/schema/GraphQLDirectiveContainer.java @@ -1,5 +1,6 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.PublicApi; import java.util.List; @@ -76,6 +77,7 @@ default List getAppliedDirectives(String directiveName) * @deprecated use {@link #hasAppliedDirective(String)} instead */ @Deprecated + @DeprecatedAt("2022-02-24") default boolean hasDirective(String directiveName) { return getAllDirectivesByName().containsKey(directiveName); } @@ -100,6 +102,7 @@ default boolean hasAppliedDirective(String directiveName) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") List getDirectives(); /** @@ -111,6 +114,7 @@ default boolean hasAppliedDirective(String directiveName) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") Map getDirectivesByName(); /** @@ -122,6 +126,7 @@ default boolean hasAppliedDirective(String directiveName) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") Map> getAllDirectivesByName(); /** @@ -135,6 +140,7 @@ default boolean hasAppliedDirective(String directiveName) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") GraphQLDirective getDirective(String directiveName); /** @@ -147,6 +153,7 @@ default boolean hasAppliedDirective(String directiveName) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") default List getDirectives(String directiveName) { return getAllDirectivesByName().getOrDefault(directiveName, emptyList()); } diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 31ea8c97ed..17bc1fae39 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import graphql.Assert; +import graphql.DeprecatedAt; import graphql.Directives; import graphql.DirectivesUtil; import graphql.Internal; @@ -418,6 +419,7 @@ public GraphQLObjectType getSubscriptionType() { * @deprecated use {@link GraphQLCodeRegistry#getFieldVisibility()} instead */ @Deprecated + @DeprecatedAt("2018-12-03") public GraphqlFieldVisibility getFieldVisibility() { return codeRegistry.getFieldVisibility(); } @@ -463,6 +465,7 @@ public GraphQLDirective getDirective(String directiveName) { * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public List getSchemaDirectives() { return schemaAppliedDirectivesHolder.getDirectives(); } @@ -478,6 +481,7 @@ public List getSchemaDirectives() { * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public Map getSchemaDirectiveByName() { return schemaAppliedDirectivesHolder.getDirectivesByName(); } @@ -493,6 +497,7 @@ public Map getSchemaDirectiveByName() { * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public Map> getAllSchemaDirectivesByName() { return schemaAppliedDirectivesHolder.getAllDirectivesByName(); } @@ -510,6 +515,7 @@ public Map> getAllSchemaDirectivesByName() { * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public GraphQLDirective getSchemaDirective(String directiveName) { return schemaAppliedDirectivesHolder.getDirective(directiveName); } @@ -525,6 +531,7 @@ public GraphQLDirective getSchemaDirective(String directiveName) { * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public List getSchemaDirectives(String directiveName) { return schemaAppliedDirectivesHolder.getDirectives(directiveName); } @@ -736,6 +743,7 @@ public Builder subscription(GraphQLObjectType subscriptionType) { * @deprecated use {@link graphql.schema.GraphQLCodeRegistry.Builder#fieldVisibility(graphql.schema.visibility.GraphqlFieldVisibility)} instead */ @Deprecated + @DeprecatedAt("2018-12-03") public Builder fieldVisibility(GraphqlFieldVisibility fieldVisibility) { this.codeRegistry = this.codeRegistry.transform(builder -> builder.fieldVisibility(fieldVisibility)); return this; @@ -866,6 +874,7 @@ public Builder introspectionSchemaType(GraphQLObjectType introspectionSchemaType * @deprecated - Use the {@link #additionalType(GraphQLType)} methods */ @Deprecated + @DeprecatedAt("2018-07-30") public GraphQLSchema build(Set additionalTypes) { return additionalTypes(additionalTypes).build(); } @@ -881,6 +890,7 @@ public GraphQLSchema build(Set additionalTypes) { * @deprecated - Use the {@link #additionalType(GraphQLType)} and {@link #additionalDirective(GraphQLDirective)} methods */ @Deprecated + @DeprecatedAt("2018-07-30") public GraphQLSchema build(Set additionalTypes, Set additionalDirectives) { return additionalTypes(additionalTypes).additionalDirectives(additionalDirectives).build(); } diff --git a/src/main/java/graphql/schema/idl/SchemaDirectiveWiringEnvironment.java b/src/main/java/graphql/schema/idl/SchemaDirectiveWiringEnvironment.java index 9440e4579a..dfe09acb43 100644 --- a/src/main/java/graphql/schema/idl/SchemaDirectiveWiringEnvironment.java +++ b/src/main/java/graphql/schema/idl/SchemaDirectiveWiringEnvironment.java @@ -1,5 +1,6 @@ package graphql.schema.idl; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.language.NamedNode; import graphql.language.NodeParentTree; @@ -42,6 +43,8 @@ public interface SchemaDirectiveWiringEnvironment getDirectives(); /** @@ -74,6 +79,8 @@ public interface SchemaDirectiveWiringEnvironment extensions; @Deprecated + @DeprecatedAt("2022-07-10") public ValidationError(ValidationErrorClassification validationErrorType) { this(newValidationError() .validationErrorType(validationErrorType)); } @Deprecated + @DeprecatedAt("2022-07-10") public ValidationError(ValidationErrorClassification validationErrorType, SourceLocation sourceLocation, String description) { this(newValidationError() .validationErrorType(validationErrorType) @@ -37,6 +40,7 @@ public ValidationError(ValidationErrorClassification validationErrorType, Source } @Deprecated + @DeprecatedAt("2022-07-10") public ValidationError(ValidationErrorType validationErrorType, SourceLocation sourceLocation, String description, List queryPath) { this(newValidationError() .validationErrorType(validationErrorType) @@ -46,6 +50,7 @@ public ValidationError(ValidationErrorType validationErrorType, SourceLocation s } @Deprecated + @DeprecatedAt("2022-07-10") public ValidationError(ValidationErrorType validationErrorType, List sourceLocations, String description) { this(newValidationError() .validationErrorType(validationErrorType) @@ -54,6 +59,7 @@ public ValidationError(ValidationErrorType validationErrorType, List sourceLocations, String description, List queryPath) { this(newValidationError() .validationErrorType(validationErrorType) From 03560080c678b87555e2460c012c38ac64ab3d59 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:25:03 +1000 Subject: [PATCH 084/294] Add more deprecation dates --- src/main/java/graphql/schema/GraphQLFieldDefinition.java | 8 ++++++++ src/main/java/graphql/schema/GraphQLInterfaceType.java | 5 +++++ src/main/java/graphql/schema/GraphQLUnionType.java | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/main/java/graphql/schema/GraphQLFieldDefinition.java b/src/main/java/graphql/schema/GraphQLFieldDefinition.java index 2772a82715..865309e37f 100644 --- a/src/main/java/graphql/schema/GraphQLFieldDefinition.java +++ b/src/main/java/graphql/schema/GraphQLFieldDefinition.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; +import graphql.DeprecatedAt; import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; @@ -86,6 +87,9 @@ public GraphQLOutputType getType() { } // to be removed in a future version when all code is in the code registry + @Internal + @Deprecated + @DeprecatedAt("2018-12-03") DataFetcher getDataFetcher() { if (dataFetcherFactory == null) { return null; @@ -307,6 +311,7 @@ public Builder type(GraphQLOutputType type) { * @deprecated use {@link graphql.schema.GraphQLCodeRegistry} instead */ @Deprecated + @DeprecatedAt("2018-12-03") public Builder dataFetcher(DataFetcher dataFetcher) { assertNotNull(dataFetcher, () -> "dataFetcher must be not null"); this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher); @@ -323,6 +328,7 @@ public Builder dataFetcher(DataFetcher dataFetcher) { * @deprecated use {@link graphql.schema.GraphQLCodeRegistry} instead */ @Deprecated + @DeprecatedAt("2018-12-03") public Builder dataFetcherFactory(DataFetcherFactory dataFetcherFactory) { assertNotNull(dataFetcherFactory, () -> "dataFetcherFactory must be not null"); this.dataFetcherFactory = dataFetcherFactory; @@ -339,6 +345,7 @@ public Builder dataFetcherFactory(DataFetcherFactory dataFetcherFactory) { * @deprecated use {@link graphql.schema.GraphQLCodeRegistry} instead */ @Deprecated + @DeprecatedAt("2018-12-03") public Builder staticValue(final Object value) { this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(environment -> value); return this; @@ -392,6 +399,7 @@ public Builder argument(GraphQLArgument.Builder builder) { * @deprecated This is a badly named method and is replaced by {@link #arguments(java.util.List)} */ @Deprecated + @DeprecatedAt("2019-02-06") public Builder argument(List arguments) { return arguments(arguments); } diff --git a/src/main/java/graphql/schema/GraphQLInterfaceType.java b/src/main/java/graphql/schema/GraphQLInterfaceType.java index 8f723cb48f..f2af03a0de 100644 --- a/src/main/java/graphql/schema/GraphQLInterfaceType.java +++ b/src/main/java/graphql/schema/GraphQLInterfaceType.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import graphql.Assert; import graphql.AssertException; +import graphql.DeprecatedAt; import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; @@ -112,6 +113,9 @@ public String getDescription() { } // to be removed in a future version when all code is in the code registry + @Internal + @Deprecated + @DeprecatedAt("2018-12-03") TypeResolver getTypeResolver() { return typeResolver; } @@ -361,6 +365,7 @@ public Builder clearFields() { @Deprecated + @DeprecatedAt("2018-12-03") public Builder typeResolver(TypeResolver typeResolver) { this.typeResolver = typeResolver; return this; diff --git a/src/main/java/graphql/schema/GraphQLUnionType.java b/src/main/java/graphql/schema/GraphQLUnionType.java index 33c6aad9be..bf39d38401 100644 --- a/src/main/java/graphql/schema/GraphQLUnionType.java +++ b/src/main/java/graphql/schema/GraphQLUnionType.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import graphql.Assert; +import graphql.DeprecatedAt; import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; @@ -100,6 +101,9 @@ public boolean isPossibleType(GraphQLObjectType graphQLObjectType) { } // to be removed in a future version when all code is in the code registry + @Internal + @Deprecated + @DeprecatedAt("2018-12-03") TypeResolver getTypeResolver() { return typeResolver; } @@ -272,6 +276,7 @@ public Builder extensionDefinitions(List extension } @Deprecated + @DeprecatedAt("2018-12-03") public Builder typeResolver(TypeResolver typeResolver) { this.typeResolver = typeResolver; return this; From fdaeff57134169b054b9f23fcc42690ce943df80 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:34:19 +1000 Subject: [PATCH 085/294] More dates --- src/main/java/graphql/cachecontrol/CacheControl.java | 10 ++++++++++ .../graphql/execution/directives/QueryDirectives.java | 4 ++++ .../InstrumentationFieldCompleteParameters.java | 4 ++++ .../InstrumentationValidationParameters.java | 2 ++ .../execution/preparsed/PreparsedDocumentProvider.java | 2 ++ .../preparsed/persisted/PersistedQueryCache.java | 2 ++ .../schema/GraphqlDirectivesContainerTypeBuilder.java | 5 +++++ 7 files changed, 29 insertions(+) diff --git a/src/main/java/graphql/cachecontrol/CacheControl.java b/src/main/java/graphql/cachecontrol/CacheControl.java index 602bec9d03..2941c8a46f 100644 --- a/src/main/java/graphql/cachecontrol/CacheControl.java +++ b/src/main/java/graphql/cachecontrol/CacheControl.java @@ -1,5 +1,6 @@ package graphql.cachecontrol; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.ExecutionResultImpl; @@ -34,6 +35,7 @@ * extensions map as per the specification. */ @Deprecated +@DeprecatedAt("2022-07-26") @PublicApi public class CacheControl { @@ -91,6 +93,7 @@ private CacheControl() { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(ResultPath path, Integer maxAge, Scope scope) { assertNotNull(path); assertNotNull(scope); @@ -108,6 +111,7 @@ public CacheControl hint(ResultPath path, Integer maxAge, Scope scope) { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(ResultPath path, Scope scope) { return hint(path, null, scope); } @@ -122,6 +126,7 @@ public CacheControl hint(ResultPath path, Scope scope) { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(ResultPath path, Integer maxAge) { return hint(path, maxAge, Scope.PUBLIC); } @@ -137,6 +142,7 @@ public CacheControl hint(ResultPath path, Integer maxAge) { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Integer maxAge, Scope scope) { assertNotNull(dataFetchingEnvironment); assertNotNull(scope); @@ -154,6 +160,7 @@ public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Intege * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Integer maxAge) { hint(dataFetchingEnvironment, maxAge, Scope.PUBLIC); return this; @@ -169,6 +176,7 @@ public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Intege * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Scope scope) { return hint(dataFetchingEnvironment, null, scope); } @@ -181,6 +189,7 @@ public CacheControl hint(DataFetchingEnvironment dataFetchingEnvironment, Scope * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public static CacheControl newCacheControl() { return new CacheControl(); } @@ -195,6 +204,7 @@ public static CacheControl newCacheControl() { * @deprecated - Apollo has deprecated the Cache Control specification */ @Deprecated + @DeprecatedAt("2022-07-26") public ExecutionResult addTo(ExecutionResult executionResult) { return ExecutionResultImpl.newExecutionResult() .from(executionResult) diff --git a/src/main/java/graphql/execution/directives/QueryDirectives.java b/src/main/java/graphql/execution/directives/QueryDirectives.java index 86cfb0d97d..97142f2069 100644 --- a/src/main/java/graphql/execution/directives/QueryDirectives.java +++ b/src/main/java/graphql/execution/directives/QueryDirectives.java @@ -1,5 +1,6 @@ package graphql.execution.directives; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.language.Field; import graphql.schema.GraphQLDirective; @@ -51,6 +52,7 @@ public interface QueryDirectives { * @deprecated - use the {@link QueryAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") Map> getImmediateDirectivesByName(); /** @@ -73,6 +75,7 @@ public interface QueryDirectives { * @deprecated - use the {@link QueryAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") List getImmediateDirective(String directiveName); /** @@ -84,5 +87,6 @@ public interface QueryDirectives { * @deprecated - use the {@link QueryAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") Map> getImmediateDirectivesByField(); } diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java index b7221468dd..254e72f47b 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStepInfo; @@ -43,6 +44,7 @@ public InstrumentationFieldCompleteParameters(ExecutionContext executionContext, * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public InstrumentationFieldCompleteParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationFieldCompleteParameters( this.executionContext, executionStrategyParameters, this.executionStepInfo, this.fetchedValue, instrumentationState); @@ -62,6 +64,7 @@ public GraphQLFieldDefinition getField() { } @Deprecated + @DeprecatedAt("2020-09-08") public ExecutionStepInfo getTypeInfo() { return getExecutionStepInfo(); } @@ -85,6 +88,7 @@ public Object getFetchedValue() { * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @SuppressWarnings("TypeParameterUnusedInFormals") public T getInstrumentationState() { //noinspection unchecked diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java index 230749b70c..a99619affa 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.PublicApi; import graphql.execution.instrumentation.Instrumentation; @@ -29,6 +30,7 @@ public InstrumentationValidationParameters(ExecutionInput executionInput, Docume * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @Override public InstrumentationValidationParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationValidationParameters( diff --git a/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java b/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java index 3cbf13c3ac..a9d8f1e842 100644 --- a/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java +++ b/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java @@ -1,6 +1,7 @@ package graphql.execution.preparsed; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.PublicSpi; @@ -27,6 +28,7 @@ public interface PreparsedDocumentProvider { * @deprecated - use {@link #getDocumentAsync(ExecutionInput executionInput, Function parseAndValidateFunction)} */ @Deprecated + @DeprecatedAt("2021-12-06") PreparsedDocumentEntry getDocument(ExecutionInput executionInput, Function parseAndValidateFunction); /** diff --git a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java index 3894b37f0e..94b7233baf 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java +++ b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java @@ -1,5 +1,6 @@ package graphql.execution.preparsed.persisted; +import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.PublicSpi; import graphql.execution.preparsed.PreparsedDocumentEntry; @@ -32,6 +33,7 @@ public interface PersistedQueryCache { * @deprecated - use {@link #getPersistedQueryDocumentAsync(Object persistedQueryId, ExecutionInput executionInput, PersistedQueryCacheMiss onCacheMiss)} */ @Deprecated + @DeprecatedAt("2021-12-06") PreparsedDocumentEntry getPersistedQueryDocument(Object persistedQueryId, ExecutionInput executionInput, PersistedQueryCacheMiss onCacheMiss) throws PersistedQueryNotFound; /** diff --git a/src/main/java/graphql/schema/GraphqlDirectivesContainerTypeBuilder.java b/src/main/java/graphql/schema/GraphqlDirectivesContainerTypeBuilder.java index 4e3a513036..b42d2eb03a 100644 --- a/src/main/java/graphql/schema/GraphqlDirectivesContainerTypeBuilder.java +++ b/src/main/java/graphql/schema/GraphqlDirectivesContainerTypeBuilder.java @@ -1,5 +1,6 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.Internal; import java.util.ArrayList; @@ -50,6 +51,7 @@ public B withAppliedDirective(GraphQLAppliedDirective.Builder builder) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public B replaceDirectives(List directives) { assertNotNull(directives, () -> "directive can't be null"); this.directives.clear(); @@ -65,6 +67,7 @@ public B replaceDirectives(List directives) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public B withDirectives(GraphQLDirective... directives) { assertNotNull(directives, () -> "directives can't be null"); this.directives.clear(); @@ -82,6 +85,7 @@ public B withDirectives(GraphQLDirective... directives) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public B withDirective(GraphQLDirective directive) { assertNotNull(directive, () -> "directive can't be null"); this.directives.add(directive); @@ -96,6 +100,7 @@ public B withDirective(GraphQLDirective directive) { * @deprecated - use the {@link GraphQLAppliedDirective} methods instead */ @Deprecated + @DeprecatedAt("2022-02-24") public B withDirective(GraphQLDirective.Builder builder) { return withDirective(builder.build()); } From e146d35b9d590d5193ce605fe2ba3d1d1e40c741 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:47:54 +1000 Subject: [PATCH 086/294] Even more dates --- .../java/graphql/execution/DataFetcherExceptionHandler.java | 1 - src/main/java/graphql/execution/DataFetcherResult.java | 2 ++ .../InstrumentationExecutionStrategyParameters.java | 3 +++ .../parameters/InstrumentationFieldFetchParameters.java | 2 ++ .../parameters/InstrumentationFieldParameters.java | 3 +++ src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java | 4 ++++ src/main/java/graphql/schema/GraphQLInputObjectField.java | 2 ++ src/main/java/graphql/schema/diff/DiffEvent.java | 2 ++ 8 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/execution/DataFetcherExceptionHandler.java b/src/main/java/graphql/execution/DataFetcherExceptionHandler.java index cc355db12f..a318ebf8df 100644 --- a/src/main/java/graphql/execution/DataFetcherExceptionHandler.java +++ b/src/main/java/graphql/execution/DataFetcherExceptionHandler.java @@ -7,7 +7,6 @@ import graphql.schema.DataFetchingEnvironment; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; /** * This is called when an exception is thrown during {@link graphql.schema.DataFetcher#get(DataFetchingEnvironment)} execution diff --git a/src/main/java/graphql/execution/DataFetcherResult.java b/src/main/java/graphql/execution/DataFetcherResult.java index ab195f9456..9b78497ed9 100644 --- a/src/main/java/graphql/execution/DataFetcherResult.java +++ b/src/main/java/graphql/execution/DataFetcherResult.java @@ -1,6 +1,7 @@ package graphql.execution; import com.google.common.collect.ImmutableList; +import graphql.DeprecatedAt; import graphql.GraphQLError; import graphql.Internal; import graphql.PublicApi; @@ -41,6 +42,7 @@ public class DataFetcherResult { */ @Internal @Deprecated + @DeprecatedAt("2019-01-11") public DataFetcherResult(T data, List errors) { this(data, errors, null); } diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java index dee0bbf3e5..7f1ec801b3 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStrategyParameters; @@ -36,6 +37,7 @@ private InstrumentationExecutionStrategyParameters(ExecutionContext executionCon * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public InstrumentationExecutionStrategyParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationExecutionStrategyParameters(executionContext, executionStrategyParameters, instrumentationState); } @@ -59,6 +61,7 @@ public ExecutionStrategyParameters getExecutionStrategyParameters() { * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @SuppressWarnings("TypeParameterUnusedInFormals") public T getInstrumentationState() { //noinspection unchecked diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java index 76fc770c51..f5b7911eb8 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStrategyParameters; @@ -40,6 +41,7 @@ private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @Override public InstrumentationFieldFetchParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationFieldFetchParameters( diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java index a6d6f0bfea..070def45a1 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java @@ -1,5 +1,6 @@ package graphql.execution.instrumentation.parameters; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStepInfo; @@ -38,6 +39,7 @@ public InstrumentationFieldParameters(ExecutionContext executionContext, Supplie * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") public InstrumentationFieldParameters withNewState(InstrumentationState instrumentationState) { return new InstrumentationFieldParameters( this.executionContext, this.executionStepInfo, instrumentationState); @@ -67,6 +69,7 @@ public ExecutionStepInfo getExecutionStepInfo() { * @deprecated state is now passed in direct to instrumentation methods */ @Deprecated + @DeprecatedAt("2022-07-26") @SuppressWarnings("TypeParameterUnusedInFormals") public T getInstrumentationState() { //noinspection unchecked diff --git a/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java b/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java index c5d7111ad6..b6fc50e92f 100644 --- a/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java +++ b/src/main/java/graphql/schema/DataFetchingEnvironmentImpl.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableMap; +import graphql.DeprecatedAt; import graphql.GraphQLContext; import graphql.Internal; import graphql.cachecontrol.CacheControl; @@ -219,6 +220,7 @@ public DataLoaderRegistry getDataLoaderRegistry() { @Override @Deprecated + @DeprecatedAt("2022-07-26") public CacheControl getCacheControl() { return cacheControl; } @@ -318,6 +320,7 @@ public Builder arguments(Supplier> arguments) { } @Deprecated + @DeprecatedAt("2021-07-05") public Builder context(Object context) { this.context = context; return this; @@ -393,6 +396,7 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) { } @Deprecated + @DeprecatedAt("2022-07-26") public Builder cacheControl(CacheControl cacheControl) { this.cacheControl = cacheControl; return this; diff --git a/src/main/java/graphql/schema/GraphQLInputObjectField.java b/src/main/java/graphql/schema/GraphQLInputObjectField.java index ec2f31013b..f97c6aca65 100644 --- a/src/main/java/graphql/schema/GraphQLInputObjectField.java +++ b/src/main/java/graphql/schema/GraphQLInputObjectField.java @@ -1,6 +1,7 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.DirectivesUtil; import graphql.PublicApi; import graphql.language.InputValueDefinition; @@ -313,6 +314,7 @@ public Builder type(GraphQLInputType type) { * @deprecated use {@link #defaultValueLiteral(Value)} */ @Deprecated + @DeprecatedAt("2021-05-10") public Builder defaultValue(Object defaultValue) { this.defaultValue = InputValueWithState.newInternalValue(defaultValue); return this; diff --git a/src/main/java/graphql/schema/diff/DiffEvent.java b/src/main/java/graphql/schema/diff/DiffEvent.java index e11088b4bf..11d07a60be 100644 --- a/src/main/java/graphql/schema/diff/DiffEvent.java +++ b/src/main/java/graphql/schema/diff/DiffEvent.java @@ -1,5 +1,6 @@ package graphql.schema.diff; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.language.TypeKind; @@ -78,6 +79,7 @@ public String toString() { * @deprecated use {@link DiffEvent#apiInfo()} instead */ @Deprecated + @DeprecatedAt("2017-12-27") public static Builder newInfo() { return new Builder().level(DiffLevel.INFO); } From 51a4524c1452d22dac0b6012b93451d4f9e5bf29 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 17:21:13 +1000 Subject: [PATCH 087/294] Fix DataFetchingEnvironment context tests --- src/test/groovy/graphql/ContextPassingDataFetcher.groovy | 2 +- .../groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy | 2 +- .../instrumentation/dataloader/DataLoaderHangingTest.groovy | 4 ++-- .../graphql/schema/DataFetchingEnvironmentImplTest.groovy | 5 ++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/groovy/graphql/ContextPassingDataFetcher.groovy b/src/test/groovy/graphql/ContextPassingDataFetcher.groovy index 1f66d52ee6..4b667bf765 100644 --- a/src/test/groovy/graphql/ContextPassingDataFetcher.groovy +++ b/src/test/groovy/graphql/ContextPassingDataFetcher.groovy @@ -25,7 +25,7 @@ class ContextPassingDataFetcher implements DataFetcher { Integer localCtx = env.getLocalContext() if (localCtx == null) { - localCtx = env.getContext() + localCtx = env.getGraphQlContext().get("key") } def newData = data + localCtx + "," diff --git a/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy b/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy index 631b824838..7b758466b5 100644 --- a/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy +++ b/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy @@ -278,7 +278,7 @@ class DataFetcherWithErrorsAndDataTest extends Specification { def result = TestUtil.graphQL(spec, runtimeWiring) .queryExecutionStrategy(executionStrategy) .build() - .execute(newExecutionInput().query(query).root("").context(1)) + .execute(newExecutionInput().query(query).root("").graphQLContext(["key": 1])) expect: diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy index 79d783a25c..3cd645343d 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy @@ -289,7 +289,7 @@ class DataLoaderHangingTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Product source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext() DataLoader personDL = dlRegistry.getDataLoader("person") return personDL.load(source.getSuppliedById()).thenApply({ person -> if (person.id == 0) { @@ -304,7 +304,7 @@ class DataLoaderHangingTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Person source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext() DataLoader companyDL = dlRegistry.getDataLoader("company") return companyDL.load(source.getCompanyId()) } diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index ef348db2c0..034a16afab 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -68,7 +68,6 @@ class DataFetchingEnvironmentImplTest extends Specification { .build() then: dfe.getRoot() == "root" - dfe.getContext() == "context" dfe.getGraphQlContext().get("key") == "context" dfe.getGraphQLSchema() == starWarsSchema dfe.getDocument() == document @@ -80,7 +79,7 @@ class DataFetchingEnvironmentImplTest extends Specification { def "create environment from existing one will copy everything to new instance"() { def dfe = newDataFetchingEnvironment() - .context("Test Context") + .context("Test Context") // Retain deprecated builder for coverage .graphQLContext(GraphQLContext.of(["key": "context"])) .source("Test Source") .root("Test Root") @@ -106,7 +105,7 @@ class DataFetchingEnvironmentImplTest extends Specification { then: dfe != dfeCopy - dfe.getContext() == dfeCopy.getContext() + dfe.getContext() == dfeCopy.getContext() // Retain deprecated method for coverage dfe.getGraphQlContext() == dfeCopy.getGraphQlContext() dfe.getSource() == dfeCopy.getSource() dfe.getRoot() == dfeCopy.getRoot() From 9a2418e0c3dcbc1842d6271b00821f6bd8d5f8e9 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 17:27:12 +1000 Subject: [PATCH 088/294] Fix ExecutionContextBuilder tests --- .../execution/ExecutionContextBuilderTest.groovy | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy index 861743f5c3..fb948b25ba 100644 --- a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy @@ -42,7 +42,7 @@ class ExecutionContextBuilderTest extends Specification { .root(root) .operationDefinition(operation) .fragmentsByName([MyFragment: fragment]) - .variables([var: 'value']) + .variables([var: 'value']) // Retain deprecated builder for test coverage .dataLoaderRegistry(dataLoaderRegistry) .cacheControl(cacheControl) .build() @@ -55,9 +55,9 @@ class ExecutionContextBuilderTest extends Specification { executionContext.mutationStrategy == mutationStrategy executionContext.subscriptionStrategy == subscriptionStrategy executionContext.root == root - executionContext.context == context + executionContext.context == context // Retain deprecated method for test coverage executionContext.graphQLContext == graphQLContext - executionContext.variables == [var: 'value'] + executionContext.variables == [var: 'value'] // Retain deprecated method for test coverage executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry @@ -94,7 +94,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.mutationStrategy == mutationStrategy executionContext.subscriptionStrategy == subscriptionStrategy executionContext.root == root - executionContext.context == context executionContext.graphQLContext == graphQLContext executionContext.coercedVariables == coercedVariables executionContext.getFragmentsByName() == [MyFragment: fragment] @@ -120,7 +119,6 @@ class ExecutionContextBuilderTest extends Specification { .root(root) .operationDefinition(operation) .fragmentsByName([MyFragment: fragment]) - .variables([var: 'value']) .coercedVariables(coercedVariables) .dataLoaderRegistry(dataLoaderRegistry) .cacheControl(cacheControl) @@ -134,7 +132,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.mutationStrategy == mutationStrategy executionContext.subscriptionStrategy == subscriptionStrategy executionContext.root == root - executionContext.context == context executionContext.graphQLContext == graphQLContext executionContext.coercedVariables == coercedVariables executionContext.getFragmentsByName() == [MyFragment: fragment] @@ -176,7 +173,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.mutationStrategy == mutationStrategy executionContext.subscriptionStrategy == subscriptionStrategy executionContext.root == root - executionContext.context == context executionContext.graphQLContext == graphQLContext executionContext.coercedVariables == coercedVariables executionContext.getFragmentsByName() == [MyFragment: fragment] @@ -199,7 +195,6 @@ class ExecutionContextBuilderTest extends Specification { .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) - .variables([:]) .coercedVariables(oldCoercedVariables) .fragmentsByName([MyFragment: fragment]) .dataLoaderRegistry(dataLoaderRegistry) @@ -209,7 +204,6 @@ class ExecutionContextBuilderTest extends Specification { when: def coercedVariables = CoercedVariables.of([var: 'value']) def executionContext = executionContextOld.transform(builder -> builder - .variables([var: 'value']) .coercedVariables(coercedVariables)) then: @@ -220,7 +214,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.mutationStrategy == mutationStrategy executionContext.subscriptionStrategy == subscriptionStrategy executionContext.root == root - executionContext.context == context executionContext.graphQLContext == graphQLContext executionContext.coercedVariables == coercedVariables executionContext.getFragmentsByName() == [MyFragment: fragment] From 102bdb72914cff984955295b1c5c8d018a98aad0 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 17:30:43 +1000 Subject: [PATCH 089/294] Fix up ExecutionInput tests --- src/test/groovy/graphql/ExecutionInputTest.groovy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/graphql/ExecutionInputTest.groovy b/src/test/groovy/graphql/ExecutionInputTest.groovy index 2a55d073d1..f0eeda64d5 100644 --- a/src/test/groovy/graphql/ExecutionInputTest.groovy +++ b/src/test/groovy/graphql/ExecutionInputTest.groovy @@ -25,13 +25,11 @@ class ExecutionInputTest extends Specification { .cacheControl(cacheControl) .variables(variables) .root(root) - .context(context) .graphQLContext({ it.of(["a": "b"]) }) .locale(Locale.GERMAN) .extensions([some: "map"]) .build() then: - executionInput.context == context executionInput.graphQLContext.get("a") == "b" executionInput.root == root executionInput.variables == variables @@ -53,6 +51,7 @@ class ExecutionInputTest extends Specification { } def "legacy context methods work"() { + // Retaining deprecated method tests for coverage when: def executionInput = ExecutionInput.newExecutionInput().query(query) .context({ builder -> builder.of("k1", "v1") } as UnaryOperator) @@ -69,6 +68,7 @@ class ExecutionInputTest extends Specification { } def "legacy context is defaulted"() { + // Retaining deprecated method tests for coverage when: def executionInput = ExecutionInput.newExecutionInput().query(query) .build() @@ -101,7 +101,6 @@ class ExecutionInputTest extends Specification { .variables(variables) .extensions([some: "map"]) .root(root) - .context(context) .graphQLContext({ it.of(["a": "b"]) }) .locale(Locale.GERMAN) .build() @@ -109,7 +108,6 @@ class ExecutionInputTest extends Specification { def executionInput = executionInputOld.transform({ bldg -> bldg.query("new query") }) then: - executionInput.context == context executionInput.graphQLContext == graphQLContext executionInput.root == root executionInput.variables == variables From ea6abca089d4ea7e683a85a432745f4e0ad2305c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 17:47:16 +1000 Subject: [PATCH 090/294] Fix InputObject tests --- .../groovy/graphql/execution/ValuesResolverTest.groovy | 8 ++++---- .../groovy/graphql/validation/ValidationUtilTest.groovy | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy index 2a1a642c5b..2fc3dae254 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy @@ -240,12 +240,12 @@ class ValuesResolverTest extends Specification { .field(newInputObjectField() .name("intKey") .type(nonNull(GraphQLInt)) - .defaultValue(3) + .defaultValueProgrammatic(3) .build()) .field(newInputObjectField() .name("stringKey") .type(GraphQLString) - .defaultValue("defaultString") + .defaultValueProgrammatic("defaultString") .build()) .build() def fieldArgument = newArgument().name("arg").type(inputObjectType).build() @@ -370,7 +370,7 @@ class ValuesResolverTest extends Specification { .field(newInputObjectField() .name("stringKey") .type(GraphQLString) - .defaultValue("defaultString")) + .defaultValueProgrammatic("defaultString")) .build() def schema = TestUtil.schemaWithInputType(inputObjectType) @@ -510,7 +510,7 @@ class ValuesResolverTest extends Specification { .name("inputObject") .build() - def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValue("hello").build() + def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValueProgrammatic("hello").build() def argument = new Argument("arg", NullValue.newNullValue().build()) when: diff --git a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy index 8ed1d8e55c..2bd6858b53 100644 --- a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy +++ b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy @@ -190,7 +190,7 @@ class ValidationUtilTest extends Specification { .field(GraphQLInputObjectField.newInputObjectField() .name("hello") .type(nonNull(GraphQLString)) - .defaultValue("default")) + .defaultValue("default")) // Retain deprecated builder for test coverage .build() def objectValue = ObjectValue.newObjectValue() From 6bde5e2ae5b5e109af5be87acba8a276dbfc9974 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 20 Aug 2022 18:30:06 +1000 Subject: [PATCH 091/294] Fix field visibility --- .../execution/ExecutionStrategyTest.groovy | 5 ++- .../FieldValidationTest.groovy | 3 +- .../GraphqlFieldVisibilityTest.groovy | 38 ++++++++++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index 7a480b4f8e..3f4d8368dd 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -21,6 +21,8 @@ import graphql.parser.Parser import graphql.schema.Coercing import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLScalarType @@ -71,7 +73,7 @@ class ExecutionStrategyTest extends Specification { .queryStrategy(executionStrategy) .mutationStrategy(executionStrategy) .subscriptionStrategy(executionStrategy) - .variables(variables) + .coercedVariables(CoercedVariables.of(variables)) .context("context") .graphQLContext(GraphQLContext.newContext().of("key","context").build()) .root("root") @@ -86,6 +88,7 @@ class ExecutionStrategyTest extends Specification { def "complete values always calls query strategy to execute more"() { given: def dataFetcher = Mock(DataFetcher) + def fieldDefinition = newFieldDefinition() .name("someField") .type(GraphQLString) diff --git a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy index 651499e69e..9208d935c3 100644 --- a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy @@ -12,6 +12,7 @@ import graphql.execution.ExecutionId import graphql.execution.ResultPath import graphql.execution.ValueUnboxer import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -308,7 +309,7 @@ class FieldValidationTest extends Specification { def execution = new Execution(strategy, strategy, strategy, instrumentation, ValueUnboxer.DEFAULT) def executionInput = ExecutionInput.newExecutionInput().query(query).variables(variables).build() - execution.execute(document, schema, ExecutionId.generate(), executionInput, SimpleInstrumentation.INSTANCE.createState()) + execution.execute(document, schema, ExecutionId.generate(), executionInput, SimpleInstrumentation.INSTANCE.createState(new InstrumentationCreateStateParameters(schema, executionInput))) } } diff --git a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy index b64495fc28..ccecb166a2 100644 --- a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy +++ b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy @@ -27,9 +27,12 @@ class GraphqlFieldVisibilityTest extends Specification { def "visibility is enforced"() { GraphqlFieldVisibility banNameVisibility = newBlock().addPattern(".*\\.name").build() + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(banNameVisibility) + .build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(banNameVisibility) + .codeRegistry(codeRegistry) .build() def graphQL = GraphQL.newGraphQL(schema).build() @@ -56,13 +59,13 @@ class GraphqlFieldVisibilityTest extends Specification { } def "introspection visibility is enforced"() { - - given: - + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(fieldVisibility) + .build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(fieldVisibility) + .codeRegistry(codeRegistry) .build() def graphQL = GraphQL.newGraphQL(schema).build() @@ -93,10 +96,12 @@ class GraphqlFieldVisibilityTest extends Specification { def "introspection turned off via field visibility"() { given: - + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(NO_INTROSPECTION_FIELD_VISIBILITY) + .build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(NO_INTROSPECTION_FIELD_VISIBILITY) + .codeRegistry(codeRegistry) .build() def graphQL = GraphQL.newGraphQL(schema).build() @@ -278,16 +283,15 @@ enum Episode { } def "ensure execution cant get to the field"() { - - when: + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(ban(['Droid.appearsIn'])) + .build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .fieldVisibility(ban(['Droid.appearsIn'])) + .codeRegistry(codeRegistry) .build() - - def executionStrategy = new AsyncExecutionStrategy() { // gives us access to this unit tested method @@ -340,9 +344,12 @@ enum Episode { er.getData() == ["hello": "world"] when: + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(ban(['InputType.closedField'])) + .build() schema = GraphQLSchema.newSchema() .query(inputQueryType) - .fieldVisibility(ban(['InputType.closedField'])) + .codeRegistry(codeRegistry) .build() graphQL = GraphQL.newGraphQL(schema).build() @@ -366,9 +373,12 @@ enum Episode { given: + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(fieldVisibility) + .build() def schema = GraphQLSchema.newSchema() .query(inputQueryType) - .fieldVisibility(fieldVisibility) + .codeRegistry(codeRegistry) .build() def graphQL = GraphQL.newGraphQL(schema).build() From 53b9d58aa7d74258a43114d4b9eccef2b8f1a48b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 21 Aug 2022 10:21:14 +1000 Subject: [PATCH 092/294] Fix tests to remove deprecated methods for schema visibility, environment context, execute --- src/test/groovy/graphql/MutationTest.groovy | 12 ++++++++++-- src/test/groovy/graphql/StarWarsQueryTest.groovy | 8 ++++++-- src/test/groovy/graphql/UnionTest.groovy | 10 ++++++++-- .../graphql/execution/ExecutionStrategyTest.groovy | 3 +-- .../SimpleDataFetcherExceptionHandlerTest.groovy | 2 +- .../PeopleCompaniesAndProductsDataLoaderTest.groovy | 8 ++++---- .../ExecutableNormalizedOperationFactoryTest.groovy | 2 +- .../graphql/schema/GraphQLCodeRegistryTest.groovy | 10 +++++++--- .../visibility/GraphqlFieldVisibilityTest.groovy | 6 +----- 9 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/test/groovy/graphql/MutationTest.groovy b/src/test/groovy/graphql/MutationTest.groovy index 68da613740..2c9bbc7aa4 100644 --- a/src/test/groovy/graphql/MutationTest.groovy +++ b/src/test/groovy/graphql/MutationTest.groovy @@ -47,7 +47,11 @@ class MutationTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(query, new MutationSchema.SubscriptionRoot(6)) + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(new MutationSchema.SubscriptionRoot(6)) + + def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(executionInput) then: @@ -93,7 +97,11 @@ class MutationTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(query, new MutationSchema.SubscriptionRoot(6)) + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(new MutationSchema.SubscriptionRoot(6)) + + def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(executionInput) then: diff --git a/src/test/groovy/graphql/StarWarsQueryTest.groovy b/src/test/groovy/graphql/StarWarsQueryTest.groovy index f28cb3fe1b..dcd92f4caa 100644 --- a/src/test/groovy/graphql/StarWarsQueryTest.groovy +++ b/src/test/groovy/graphql/StarWarsQueryTest.groovy @@ -190,7 +190,7 @@ class StarWarsQueryTest extends Specification { ] ] when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data // Retain deprecated method for test coverage then: result == expected @@ -212,7 +212,11 @@ class StarWarsQueryTest extends Specification { human: null ] when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(null) + .variables(params) + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(executionInput).data then: result == expected diff --git a/src/test/groovy/graphql/UnionTest.groovy b/src/test/groovy/graphql/UnionTest.groovy index 4fb9f2af25..fcd308dc31 100644 --- a/src/test/groovy/graphql/UnionTest.groovy +++ b/src/test/groovy/graphql/UnionTest.groovy @@ -96,7 +96,10 @@ class UnionTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(query, GarfieldSchema.john) + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(GarfieldSchema.john) + def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(executionInput) then: executionResult.data == expectedResult @@ -148,7 +151,10 @@ class UnionTest extends Specification { ] ] when: - def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(query, GarfieldSchema.john) + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(GarfieldSchema.john) + def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(executionInput) then: executionResult.data == expectedResult diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index 3f4d8368dd..2b9009b01d 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -523,10 +523,9 @@ class ExecutionStrategyTest extends Specification { 1 * dataFetcher.get(_) >> { args -> environment = args[0] } environment.fieldDefinition == fieldDefinition environment.graphQLSchema == schema - environment.context == "context" environment.graphQlContext.get("key") == "context" environment.source == "source" - environment.fields == [field] + environment.fields == [field] // Retain deprecated for test coverage environment.root == "root" environment.parentType == objectType environment.arguments == ["arg1": "argVal"] diff --git a/src/test/groovy/graphql/execution/SimpleDataFetcherExceptionHandlerTest.groovy b/src/test/groovy/graphql/execution/SimpleDataFetcherExceptionHandlerTest.groovy index eac8b6f44b..1980db0816 100644 --- a/src/test/groovy/graphql/execution/SimpleDataFetcherExceptionHandlerTest.groovy +++ b/src/test/groovy/graphql/execution/SimpleDataFetcherExceptionHandlerTest.groovy @@ -52,7 +52,7 @@ class SimpleDataFetcherExceptionHandlerTest extends Specification { when: DataFetcherExceptionHandler handler = new MyHandler() def handlerParameters = mkParams(new RuntimeException("RTE")) - def result = handler.onException(handlerParameters) + def result = handler.onException(handlerParameters) // Retain deprecated method for test coverage then: result.errors[0] instanceof ExceptionWhileDataFetching diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy index f58a4a9c9d..1b36ec4ecb 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy @@ -105,7 +105,7 @@ class PeopleCompaniesAndProductsDataLoaderTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Product source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext().get("registry") DataLoader personDL = dlRegistry.getDataLoader("person") return personDL.load(source.getSuppliedById()) } @@ -115,7 +115,7 @@ class PeopleCompaniesAndProductsDataLoaderTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Product source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext().get("registry") DataLoader personDL = dlRegistry.getDataLoader("person") return personDL.loadMany(source.getMadeByIds()) @@ -126,7 +126,7 @@ class PeopleCompaniesAndProductsDataLoaderTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Person source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext().get("registry") DataLoader companyDL = dlRegistry.getDataLoader("company") return companyDL.load(source.getCompanyId()) } @@ -191,7 +191,7 @@ class PeopleCompaniesAndProductsDataLoaderTest extends Specification { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query(query) - .context(registry) + .graphQLContext(["registry": registry]) .dataLoaderRegistry(registry) .build() diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy index 1b18fa97f3..74bf41dd0a 100644 --- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy @@ -1314,7 +1314,7 @@ schema { private void assertValidQuery(GraphQLSchema graphQLSchema, String query, Map variables = [:]) { GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build() - assert graphQL.execute(query, null, variables).errors.size() == 0 + assert graphQL.execute(query, null, variables).errors.size() == 0 // Retain deprecated method for test coverage } def "normalized arguments"() { diff --git a/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy b/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy index b610b6c1a7..bff5f7f211 100644 --- a/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy @@ -167,11 +167,15 @@ class GraphQLCodeRegistryTest extends Specification { } def "schema delegates field visibility to code registry"() { - when: - def schema = GraphQLSchema.newSchema().fieldVisibility(new NamedFieldVisibility("B")).query(objectType("query")).build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .fieldVisibility(new NamedFieldVisibility("B")) + .build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(objectType("query")) + .build() then: - (schema.getFieldVisibility() as NamedFieldVisibility).name == "B" (schema.getCodeRegistry().getFieldVisibility() as NamedFieldVisibility).name == "B" } diff --git a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy index ccecb166a2..1c98b1f85d 100644 --- a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy +++ b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy @@ -25,14 +25,10 @@ import static graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility.NO class GraphqlFieldVisibilityTest extends Specification { def "visibility is enforced"() { - GraphqlFieldVisibility banNameVisibility = newBlock().addPattern(".*\\.name").build() - GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .fieldVisibility(banNameVisibility) - .build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) - .codeRegistry(codeRegistry) + .fieldVisibility(banNameVisibility) // Retain deprecated builder for test coverage .build() def graphQL = GraphQL.newGraphQL(schema).build() From 5fa0962949d120bfb13241b8e73539bbe15314c8 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 21 Aug 2022 19:53:19 +1000 Subject: [PATCH 093/294] Change Instrumentation production implementations to use non deprecated methods. --- .../MaxQueryComplexityInstrumentation.java | 22 +++++----- .../MaxQueryDepthInstrumentation.java | 8 ++-- .../FieldValidationInstrumentation.java | 5 ++- .../threadpools/ExecutorInstrumentation.java | 14 ++++--- .../tracing/TracingInstrumentation.java | 20 +++++----- ...xQueryComplexityInstrumentationTest.groovy | 40 ++++++++++++------- .../MaxQueryDepthInstrumentationTest.groovy | 4 +- .../ExecutorInstrumentationTest.groovy | 12 +++--- .../PreparsedDocumentProviderTest.groovy | 20 +++++----- 9 files changed, 83 insertions(+), 62 deletions(-) diff --git a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java index a24191fa4d..bbe2b85bf0 100644 --- a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java @@ -11,6 +11,7 @@ import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.validation.ValidationError; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +27,7 @@ /** * Prevents execution if the query complexity is greater than the specified maxComplexity. - * + *

* Use the {@code Function} parameter to supply a function to perform a custom action when the max complexity * is exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @@ -87,25 +88,26 @@ public InstrumentationState createState(InstrumentationCreateStateParameters par return new State(); } + @Override - public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - State state = parameters.getInstrumentationState(); + public @Nullable InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { + State state = (State) rawState; // for API backwards compatibility reasons we capture the validation parameters, so we can put them into QueryComplexityInfo state.instrumentationValidationParameters.set(parameters); return noOp(); } @Override - public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters) { - State state = instrumentationExecuteOperationParameters.getInstrumentationState(); + public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters, InstrumentationState rawState) { + State state = (State) rawState; QueryTraverser queryTraverser = newQueryTraverser(instrumentationExecuteOperationParameters.getExecutionContext()); Map valuesByParent = new LinkedHashMap<>(); queryTraverser.visitPostOrder(new QueryVisitorStub() { @Override public void visitField(QueryVisitorFieldEnvironment env) { - int childsComplexity = valuesByParent.getOrDefault(env, 0); - int value = calculateComplexity(env, childsComplexity); + int childComplexity = valuesByParent.getOrDefault(env, 0); + int value = calculateComplexity(env, childComplexity); valuesByParent.compute(env.getParentEnvironment(), (key, oldValue) -> ofNullable(oldValue).orElse(0) + value @@ -136,7 +138,7 @@ public void visitField(QueryVisitorFieldEnvironment env) { * @param totalComplexity the complexity of the query * @param maxComplexity the maximum complexity allowed * - * @return a instance of AbortExecutionException + * @return an instance of AbortExecutionException */ protected AbortExecutionException mkAbortException(int totalComplexity, int maxComplexity) { return new AbortExecutionException("maximum query complexity exceeded " + totalComplexity + " > " + maxComplexity); @@ -151,12 +153,12 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) { .build(); } - private int calculateComplexity(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment, int childsComplexity) { + private int calculateComplexity(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment, int childComplexity) { if (queryVisitorFieldEnvironment.isTypeNameIntrospectionField()) { return 0; } FieldComplexityEnvironment fieldComplexityEnvironment = convertEnv(queryVisitorFieldEnvironment); - return fieldComplexityCalculator.calculate(fieldComplexityEnvironment, childsComplexity); + return fieldComplexityCalculator.calculate(fieldComplexityEnvironment, childComplexity); } private FieldComplexityEnvironment convertEnv(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment) { diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index 3a79f9ee0b..20a6e92990 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -5,8 +5,10 @@ import graphql.execution.AbortExecutionException; import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +18,7 @@ /** * Prevents execution if the query depth is greater than the specified maxDepth. - * + *

* Use the {@code Function} parameter to supply a function to perform a custom action when the max depth is * exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @@ -49,7 +51,7 @@ public MaxQueryDepthInstrumentation(int maxDepth, Function beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { + public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { QueryTraverser queryTraverser = newQueryTraverser(parameters.getExecutionContext()); int depth = queryTraverser.reducePreOrder((env, acc) -> Math.max(getPathLength(env.getParentEnvironment()), acc), 0); if (log.isDebugEnabled()) { @@ -73,7 +75,7 @@ public InstrumentationContext beginExecuteOperation(Instrumenta * @param depth the depth of the query * @param maxDepth the maximum depth allowed * - * @return a instance of AbortExecutionException + * @return an instance of AbortExecutionException */ protected AbortExecutionException mkAbortException(int depth, int maxDepth) { return new AbortExecutionException("maximum query depth exceeded " + depth + " > " + maxDepth); diff --git a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java index 19fa513628..2530648124 100644 --- a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java @@ -5,8 +5,10 @@ import graphql.PublicApi; import graphql.execution.AbortExecutionException; import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -36,8 +38,7 @@ public FieldValidationInstrumentation(FieldValidation fieldValidation) { } @Override - public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { - + public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { List errors = FieldValidationSupport.validateFieldsAndArguments(fieldValidation, parameters.getExecutionContext()); if (errors != null && !errors.isEmpty()) { throw new AbortExecutionException(errors); diff --git a/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java b/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java index d14eec10ff..bc1de2921b 100644 --- a/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java @@ -5,10 +5,12 @@ import graphql.Internal; import graphql.TrivialDataFetcher; import graphql.execution.Async; +import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -24,13 +26,13 @@ * This instrumentation can be used to control on what thread calls to {@link DataFetcher}s happen on. *

* If your data fetching is inherently IO bound then you could use a IO oriented thread pool for your fetches and transfer control - * back to a CPU oriented thread pool and allow graphql-java code to run the post processing of results there. + * back to a CPU oriented thread pool and allow graphql-java code to run the post-processing of results there. *

* An IO oriented thread pool is typically a multiple of {@link Runtime#availableProcessors()} while a CPU oriented thread pool * is typically no more than {@link Runtime#availableProcessors()}. *

- * The instrumentation will use the {@link graphql.execution.instrumentation.Instrumentation#instrumentDataFetcher(DataFetcher, InstrumentationFieldFetchParameters)} - * method to change your data fetchers so they are executed on a thread pool dedicated to fetching (if you provide one). + * The instrumentation will use the {@link graphql.execution.instrumentation.Instrumentation#instrumentDataFetcher(DataFetcher, InstrumentationFieldFetchParameters, InstrumentationState)} + * method to change your data fetchers, so they are executed on a thread pool dedicated to fetching (if you provide one). *

* Once the data fetcher value is returns it will transfer control back to a processing thread pool (if you provide one). *

@@ -106,7 +108,7 @@ public ExecutorInstrumentation build() { } @Override - public DataFetcher instrumentDataFetcher(DataFetcher originalDataFetcher, InstrumentationFieldFetchParameters parameters) { + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher originalDataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState state) { if (originalDataFetcher instanceof TrivialDataFetcher) { return originalDataFetcher; } @@ -117,7 +119,7 @@ public DataFetcher instrumentDataFetcher(DataFetcher originalDataFetcher, // the CF will be left running on that fetch executors thread invokedCF = CompletableFuture.supplyAsync(invokedAsync(originalDataFetcher, environment), fetchExecutor); } else { - invokedCF = invokedSynch(originalDataFetcher, environment); + invokedCF = invokedSync(originalDataFetcher, environment); } if (processingExecutor != null) { invokedCF = invokedCF.thenApplyAsync(processingControl(), processingExecutor); @@ -136,7 +138,7 @@ private Supplier> invokedAsync(DataFetcher originalDataFet }; } - private CompletableFuture> invokedSynch(DataFetcher originalDataFetcher, DataFetchingEnvironment environment) { + private CompletableFuture> invokedSync(DataFetcher originalDataFetcher, DataFetchingEnvironment environment) { actionObserver.accept(FETCHING); return CompletableFuture.completedFuture(invokeOriginalDF(originalDataFetcher, environment)); } diff --git a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java index 80231608b0..eb6bd87d53 100644 --- a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java @@ -8,11 +8,13 @@ import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.language.Document; import graphql.validation.ValidationError; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.LinkedHashMap; @@ -69,15 +71,15 @@ public TracingInstrumentation(Options options) { private final Options options; @Override - public InstrumentationState createState() { + public @Nullable InstrumentationState createState(InstrumentationCreateStateParameters parameters) { return new TracingSupport(options.includeTrivialDataFetchers); } @Override - public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { + public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { Map currentExt = executionResult.getExtensions(); - TracingSupport tracingSupport = parameters.getInstrumentationState(); + TracingSupport tracingSupport = (TracingSupport) rawState; Map withTracingExt = new LinkedHashMap<>(currentExt == null ? ImmutableKit.emptyMap() : currentExt); withTracingExt.put("tracing", tracingSupport.snapshotTracingData()); @@ -85,22 +87,22 @@ public CompletableFuture instrumentExecutionResult(ExecutionRes } @Override - public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - TracingSupport tracingSupport = parameters.getInstrumentationState(); + public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState rawState) { + TracingSupport tracingSupport = (TracingSupport) rawState; TracingSupport.TracingContext ctx = tracingSupport.beginField(parameters.getEnvironment(), parameters.isTrivialDataFetcher()); return whenCompleted((result, t) -> ctx.onEnd()); } @Override - public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { - TracingSupport tracingSupport = parameters.getInstrumentationState(); + public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters,InstrumentationState rawState) { + TracingSupport tracingSupport = (TracingSupport) rawState; TracingSupport.TracingContext ctx = tracingSupport.beginParse(); return whenCompleted((result, t) -> ctx.onEnd()); } @Override - public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - TracingSupport tracingSupport = parameters.getInstrumentationState(); + public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { + TracingSupport tracingSupport = (TracingSupport) rawState; TracingSupport.TracingContext ctx = tracingSupport.beginValidation(); return whenCompleted((result, t) -> ctx.onEnd()); } diff --git a/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy index 3670b4fe1d..cee882ccae 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryComplexityInstrumentationTest.groovy @@ -6,6 +6,7 @@ import graphql.execution.AbortExecutionException import graphql.execution.ExecutionContext import graphql.execution.ExecutionContextBuilder import graphql.execution.ExecutionId +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters import graphql.language.Document @@ -40,9 +41,10 @@ class MaxQueryComplexityInstrumentationTest extends Specification { """) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(10) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) + def state = createInstrumentationState(queryComplexityInstrumentation) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema, state) when: - queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters, state) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 11 > 10" @@ -62,9 +64,10 @@ class MaxQueryComplexityInstrumentationTest extends Specification { """) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(1) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) + def state = createInstrumentationState(queryComplexityInstrumentation) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema, state) when: - queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters, state) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 2 > 1" @@ -89,9 +92,10 @@ class MaxQueryComplexityInstrumentationTest extends Specification { def calculator = Mock(FieldComplexityCalculator) MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(5, calculator) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) + def state = createInstrumentationState(queryComplexityInstrumentation) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema, state) when: - queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters, state) then: 1 * calculator.calculate({ FieldComplexityEnvironment env -> env.field.name == "scalar" }, 0) >> 10 @@ -128,9 +132,10 @@ class MaxQueryComplexityInstrumentationTest extends Specification { } MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(10, maxQueryComplexityExceededFunction) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) + def state = createInstrumentationState(queryComplexityInstrumentation) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema, state) when: - queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters, state) then: customFunctionCalled notThrown(Exception) @@ -151,24 +156,29 @@ class MaxQueryComplexityInstrumentationTest extends Specification { MaxQueryComplexityInstrumentation queryComplexityInstrumentation = new MaxQueryComplexityInstrumentation(0) ExecutionInput executionInput = Mock(ExecutionInput) - InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema) + def state = createInstrumentationState(queryComplexityInstrumentation) + InstrumentationExecuteOperationParameters executeOperationParameters = createExecuteOperationParameters(queryComplexityInstrumentation, executionInput, query, schema, state) when: - queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters) + queryComplexityInstrumentation.beginExecuteOperation(executeOperationParameters, state) then: def e = thrown(AbortExecutionException) e.message == "maximum query complexity exceeded 1 > 0" } - private InstrumentationExecuteOperationParameters createExecuteOperationParameters(MaxQueryComplexityInstrumentation queryComplexityInstrumentation, ExecutionInput executionInput, Document query, GraphQLSchema schema) { + private InstrumentationExecuteOperationParameters createExecuteOperationParameters(MaxQueryComplexityInstrumentation queryComplexityInstrumentation, ExecutionInput executionInput, Document query, GraphQLSchema schema, InstrumentationState state) { // we need to run N steps to create instrumentation state - def instrumentationState = queryComplexityInstrumentation.createState(null) - def validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, instrumentationState) - queryComplexityInstrumentation.beginValidation(validationParameters) + def validationParameters = new InstrumentationValidationParameters(executionInput, query, schema, state) + queryComplexityInstrumentation.beginValidation(validationParameters, state) def executionContext = executionCtx(executionInput, query, schema) - def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext).withNewState(instrumentationState) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) executeOperationParameters } + def createInstrumentationState(MaxQueryComplexityInstrumentation queryComplexityInstrumentation) { + queryComplexityInstrumentation.createState(null) + } + + private ExecutionContext executionCtx(ExecutionInput executionInput, Document query, GraphQLSchema schema) { ExecutionContextBuilder.newExecutionContextBuilder() .executionInput(executionInput).document(query).graphQLSchema(schema).executionId(ExecutionId.generate()) diff --git a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy index 11ac79bc7f..f7f66e9142 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy @@ -43,7 +43,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { def executionContext = executionCtx(executionInput, query, schema) def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters, null) then: def e = thrown(AbortExecutionException) e.message.contains("maximum query depth exceeded 7 > 6") @@ -102,7 +102,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { def executionContext = executionCtx(executionInput, query, schema) def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters, null) then: calledFunction notThrown(Exception) diff --git a/src/test/groovy/graphql/execution/instrumentation/threadpools/ExecutorInstrumentationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/threadpools/ExecutorInstrumentationTest.groovy index 227d960f89..4a4a453ce6 100644 --- a/src/test/groovy/graphql/execution/instrumentation/threadpools/ExecutorInstrumentationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/threadpools/ExecutorInstrumentationTest.groovy @@ -77,7 +77,7 @@ class ExecutorInstrumentationTest extends Specification { def "can handle a data fetcher that throws exceptions"() { when: DataFetcher df = { env -> throw new RuntimeException("BANG") } - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null,null) def returnedValue = modifiedDataFetcher.get(null) then: @@ -96,7 +96,7 @@ class ExecutorInstrumentationTest extends Specification { when: DataFetcher df = PropertyDataFetcher.fetching({ o -> "trivial" }) - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null,null) def returnedValue = modifiedDataFetcher.get(dfEnv("source")) then: @@ -111,7 +111,7 @@ class ExecutorInstrumentationTest extends Specification { instrumentation = build(FetchExecutor, ProcessingExecutor, observer) DataFetcher df = { env -> currentThread().getName() } - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null, null) def returnedValue = modifiedDataFetcher.get(null) then: @@ -131,7 +131,7 @@ class ExecutorInstrumentationTest extends Specification { instrumentation = build(FetchExecutor, null, observer) DataFetcher df = { env -> currentThread().getName() } - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null, null) def returnedValue = modifiedDataFetcher.get(null) then: @@ -152,7 +152,7 @@ class ExecutorInstrumentationTest extends Specification { instrumentation = build(null, ProcessingExecutor, observer) DataFetcher df = { env -> currentThread().getName() } - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null, null) def returnedValue = modifiedDataFetcher.get(null) then: @@ -171,7 +171,7 @@ class ExecutorInstrumentationTest extends Specification { instrumentation = build(FetchExecutor, ProcessingExecutor, observer) DataFetcher df = { env -> CompletableFuture.completedFuture(currentThread().getName()) } - def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null) + def modifiedDataFetcher = instrumentation.instrumentDataFetcher(df, null, null) def returnedValue = modifiedDataFetcher.get(null) then: diff --git a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy index 5bc311c843..6f48274141 100644 --- a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy +++ b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy @@ -6,8 +6,9 @@ import graphql.GraphQL import graphql.StarWarsSchema import graphql.execution.AsyncExecutionStrategy import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.LegacyTestingInstrumentation +import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.language.Document import graphql.parser.Parser @@ -16,6 +17,7 @@ import spock.lang.Specification import java.util.function.Function import static graphql.ExecutionInput.newExecutionInput +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp class PreparsedDocumentProviderTest extends Specification { @@ -110,14 +112,14 @@ class PreparsedDocumentProviderTest extends Specification { .instrumentation(instrumentation) .preparsedDocumentProvider(preparsedCache) .build() - .execute(ExecutionInput.newExecutionInput().query(query).build()).data + .execute(newExecutionInput().query(query).build()).data def data2 = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema) .queryExecutionStrategy(strategy) .instrumentation(instrumentationPreparsed) .preparsedDocumentProvider(preparsedCache) .build() - .execute(ExecutionInput.newExecutionInput().query(query).build()).data + .execute(newExecutionInput().query(query).build()).data then: @@ -145,12 +147,12 @@ class PreparsedDocumentProviderTest extends Specification { def result1 = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema) .preparsedDocumentProvider(preparsedCache) .build() - .execute(ExecutionInput.newExecutionInput().query(query).build()) + .execute(newExecutionInput().query(query).build()) def result2 = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema) .preparsedDocumentProvider(preparsedCache) .build() - .execute(ExecutionInput.newExecutionInput().query(query).build()) + .execute(newExecutionInput().query(query).build()) then: "Both the first and the second result should give the same validation error" result1.errors.size() == 1 @@ -165,9 +167,9 @@ class PreparsedDocumentProviderTest extends Specification { ExecutionInput capturedInput @Override - InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { + InstrumentationContext beginParse(InstrumentationExecutionParameters parameters, InstrumentationState state) { capturedInput = parameters.getExecutionInput() - return super.beginParse(parameters) + return noOp() } } @@ -206,14 +208,14 @@ class PreparsedDocumentProviderTest extends Specification { .preparsedDocumentProvider(documentProvider) .instrumentation(instrumentationA) .build() - .execute(ExecutionInput.newExecutionInput().query("#A").build()) + .execute(newExecutionInput().query("#A").build()) def instrumentationB = new InputCapturingInstrumentation() def resultB = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema) .preparsedDocumentProvider(documentProvider) .instrumentation(instrumentationB) .build() - .execute(ExecutionInput.newExecutionInput().query("#B").build()) + .execute(newExecutionInput().query("#B").build()) expect: From 4652d3db016885d6ce690946c7c5ae3ba912ac1c Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Mon, 22 Aug 2022 10:43:47 +1000 Subject: [PATCH 094/294] Fix field visibility bug with enum with enum args (#2926) Fixes a bug that happened when the field visibility transformation was applied to a schema that has some type(s) reachable only via directive definitions. --- .../FieldVisibilitySchemaTransformation.java | 18 +++++- ...dVisibilitySchemaTransformationTest.groovy | 63 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java b/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java index a290258838..303b81b9c8 100644 --- a/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java +++ b/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java @@ -1,10 +1,12 @@ package graphql.schema.transform; +import com.google.common.collect.ImmutableList; import graphql.PublicApi; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLImplementingType; import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLNamedSchemaElement; import graphql.schema.GraphQLNamedType; @@ -60,7 +62,7 @@ public final GraphQLSchema apply(GraphQLSchema schema) { Set markedForRemovalTypes = new HashSet<>(); // query, mutation, and subscription types should not be removed - final Set protectedTypeNames = getRootTypes(schema).stream() + final Set protectedTypeNames = getOperationTypes(schema).stream() .map(GraphQLObjectType::getName) .collect(Collectors.toSet()); @@ -216,6 +218,7 @@ public TraversalControl visitGraphQLType(GraphQLSchemaElement node, !observedAfterTransform.contains(node) && (node instanceof GraphQLObjectType || node instanceof GraphQLEnumType || + node instanceof GraphQLInputObjectType || node instanceof GraphQLInterfaceType || node instanceof GraphQLUnionType)) { @@ -250,12 +253,21 @@ public TraversalControl visitGraphQLType(GraphQLSchemaElement node, } } - private List getRootTypes(GraphQLSchema schema) { + private List getRootTypes(GraphQLSchema schema) { + return ImmutableList.builder() + .addAll(getOperationTypes(schema)) + // Include directive definitions as roots, since they won't be removed in the filtering process. + // Some types (enums, input types, etc.) might be reachable only by directive definitions (and + // not by other types or fields). + .addAll(schema.getDirectives()) + .build(); + } + + private List getOperationTypes(GraphQLSchema schema) { return Stream.of( schema.getQueryType(), schema.getSubscriptionType(), schema.getMutationType() ).filter(Objects::nonNull).collect(Collectors.toList()); } - } diff --git a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy index c6f25fd367..a6f82018a2 100644 --- a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy +++ b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy @@ -1178,4 +1178,67 @@ class FieldVisibilitySchemaTransformationTest extends Specification { (restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null restrictedSchema.getType("BillingStatus") != null } + + def "can remove a field with a directive containing enum argument"() { + given: + GraphQLSchema schema = TestUtil.schema(""" + + directive @private(privateType: SecretType) on FIELD_DEFINITION + enum SecretType { + SUPER_SECRET + NOT_SO_SECRET + } + + type Query { + account: Account + } + + type Account { + name: String + billingStatus: BillingStatus @private(privateType: NOT_SO_SECRET) + } + + type BillingStatus { + accountNumber: String + } + """) + + when: + GraphQLSchema restrictedSchema = visibilitySchemaTransformation.apply(schema) + + then: + (restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null + restrictedSchema.getType("BillingStatus") == null + } + + def "can remove a field with a directive containing type argument"() { + given: + GraphQLSchema schema = TestUtil.schema(""" + + directive @private(privateType: SecretType) on FIELD_DEFINITION + input SecretType { + description: String + } + + type Query { + account: Account + } + + type Account { + name: String + billingStatus: BillingStatus @private(privateType: { description: "secret" }) + } + + type BillingStatus { + accountNumber: String + } + """) + + when: + GraphQLSchema restrictedSchema = visibilitySchemaTransformation.apply(schema) + + then: + (restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null + restrictedSchema.getType("BillingStatus") == null + } } From 9329d2f903abad29d3958bdb407ae07a946d1b88 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 12:46:13 +1000 Subject: [PATCH 095/294] Fix up DataLoader test --- .../instrumentation/dataloader/DataLoaderHangingTest.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy index 3cd645343d..e8352969c1 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy @@ -289,7 +289,7 @@ class DataLoaderHangingTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Product source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getGraphQlContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext().get("registry") DataLoader personDL = dlRegistry.getDataLoader("person") return personDL.load(source.getSuppliedById()).thenApply({ person -> if (person.id == 0) { @@ -304,7 +304,7 @@ class DataLoaderHangingTest extends Specification { @Override Object get(DataFetchingEnvironment environment) { Person source = environment.getSource() - DataLoaderRegistry dlRegistry = environment.getGraphQlContext() + DataLoaderRegistry dlRegistry = environment.getGraphQlContext().get("registry") DataLoader companyDL = dlRegistry.getDataLoader("company") return companyDL.load(source.getCompanyId()) } @@ -355,7 +355,7 @@ class DataLoaderHangingTest extends Specification { ExecutionInput executionInput = newExecutionInput() .query(query) - .context(registry) + .graphQLContext(["registry": registry]) .dataLoaderRegistry(registry) .build() From 04230fb013ddc4aa19d45eca212f7afd6035033f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 13:09:01 +1000 Subject: [PATCH 096/294] Clean up CacheControl tests --- .../groovy/graphql/ExecutionInputTest.groovy | 14 +--- .../cachecontrol/CacheControlTest.groovy | 68 +++++++++++++++++++ .../ExecutionContextBuilderTest.groovy | 12 ---- .../DataFetchingEnvironmentImplTest.groovy | 8 +-- 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/test/groovy/graphql/ExecutionInputTest.groovy b/src/test/groovy/graphql/ExecutionInputTest.groovy index f0eeda64d5..f29b637f53 100644 --- a/src/test/groovy/graphql/ExecutionInputTest.groovy +++ b/src/test/groovy/graphql/ExecutionInputTest.groovy @@ -1,6 +1,5 @@ package graphql -import graphql.cachecontrol.CacheControl import graphql.execution.ExecutionId import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment @@ -13,16 +12,13 @@ class ExecutionInputTest extends Specification { def query = "query { hello }" def registry = new DataLoaderRegistry() - def cacheControl = CacheControl.newCacheControl() def root = "root" - def context = "context" def variables = [key: "value"] def "build works"() { when: def executionInput = ExecutionInput.newExecutionInput().query(query) .dataLoaderRegistry(registry) - .cacheControl(cacheControl) .variables(variables) .root(root) .graphQLContext({ it.of(["a": "b"]) }) @@ -35,7 +31,6 @@ class ExecutionInputTest extends Specification { executionInput.variables == variables executionInput.rawVariables.toMap() == variables executionInput.dataLoaderRegistry == registry - executionInput.cacheControl == cacheControl executionInput.query == query executionInput.locale == Locale.GERMAN executionInput.extensions == [some: "map"] @@ -97,7 +92,6 @@ class ExecutionInputTest extends Specification { when: def executionInputOld = ExecutionInput.newExecutionInput().query(query) .dataLoaderRegistry(registry) - .cacheControl(cacheControl) .variables(variables) .extensions([some: "map"]) .root(root) @@ -112,7 +106,6 @@ class ExecutionInputTest extends Specification { executionInput.root == root executionInput.variables == variables executionInput.dataLoaderRegistry == registry - executionInput.cacheControl == cacheControl executionInput.locale == Locale.GERMAN executionInput.extensions == [some: "map"] executionInput.query == "new query" @@ -122,7 +115,6 @@ class ExecutionInputTest extends Specification { when: def executionInputOld = ExecutionInput.newExecutionInput().query(query) .dataLoaderRegistry(registry) - .cacheControl(cacheControl) .extensions([some: "map"]) .root(root) .graphQLContext({ it.of(["a": "b"]) }) @@ -138,7 +130,6 @@ class ExecutionInputTest extends Specification { executionInput.root == root executionInput.rawVariables.toMap() == variables executionInput.dataLoaderRegistry == registry - executionInput.cacheControl == cacheControl executionInput.locale == Locale.GERMAN executionInput.extensions == [some: "map"] executionInput.query == "new query" @@ -151,7 +142,6 @@ class ExecutionInputTest extends Specification { .build() then: executionInput.query == "{ q }" - executionInput.cacheControl != null executionInput.locale == Locale.ENGLISH executionInput.dataLoaderRegistry != null executionInput.variables == [:] @@ -167,7 +157,6 @@ class ExecutionInputTest extends Specification { DataFetcher df = { DataFetchingEnvironment env -> return [ "locale" : env.getLocale().getDisplayName(Locale.ENGLISH), - "cacheControl" : env.getCacheControl() == cacheControl, "executionId" : env.getExecutionId().toString(), "graphqlContext": env.getGraphQlContext().get("a") @@ -180,7 +169,6 @@ class ExecutionInputTest extends Specification { ExecutionInput executionInput = ExecutionInput.newExecutionInput() .query("{ fetch }") .locale(Locale.GERMAN) - .cacheControl(cacheControl) .executionId(ExecutionId.from("ID123")) .build() executionInput.getGraphQLContext().putAll([a: "b"]) @@ -189,6 +177,6 @@ class ExecutionInputTest extends Specification { then: er.errors.isEmpty() - er.data["fetch"] == "{locale=German, cacheControl=true, executionId=ID123, graphqlContext=b}" + er.data["fetch"] == "{locale=German, executionId=ID123, graphqlContext=b}" } } diff --git a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy index c2d5e1a752..8518552e50 100644 --- a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy +++ b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy @@ -2,12 +2,25 @@ package graphql.cachecontrol import graphql.ExecutionInput import graphql.ExecutionResultImpl +import graphql.GraphQLContext import graphql.TestUtil +import graphql.execution.CoercedVariables +import graphql.execution.ExecutionContextBuilder +import graphql.execution.ExecutionId +import graphql.execution.ExecutionStrategy import graphql.execution.ResultPath +import graphql.execution.instrumentation.Instrumentation +import graphql.language.Document +import graphql.language.FragmentDefinition +import graphql.language.OperationDefinition +import graphql.parser.Parser import graphql.schema.DataFetcher +import graphql.schema.GraphQLSchema +import org.dataloader.DataLoaderRegistry import spock.lang.Specification class CacheControlTest extends Specification { + // All tests in this file will be deleted when CacheControl code is removed. def "can build up hints when there is no extensions present"() { def cc = CacheControl.newCacheControl() @@ -115,4 +128,59 @@ class CacheControlTest extends Specification { ] ] } + + def "transform works and copies values with cache control"() { + // Retain this ExecutionContext CacheControl test for coverage. + given: + def cacheControl = CacheControl.newCacheControl() + def oldCoercedVariables = CoercedVariables.emptyVariables() + Instrumentation instrumentation = Mock(Instrumentation) + ExecutionStrategy queryStrategy = Mock(ExecutionStrategy) + ExecutionStrategy mutationStrategy = Mock(ExecutionStrategy) + ExecutionStrategy subscriptionStrategy = Mock(ExecutionStrategy) + GraphQLSchema schema = Mock(GraphQLSchema) + def executionId = ExecutionId.generate() + def graphQLContext = GraphQLContext.newContext().build() + def root = "root" + Document document = new Parser().parseDocument("query myQuery(\$var: String){...MyFragment} fragment MyFragment on Query{foo}") + def operation = document.definitions[0] as OperationDefinition + def fragment = document.definitions[1] as FragmentDefinition + def dataLoaderRegistry = new DataLoaderRegistry() + + def executionContextOld = new ExecutionContextBuilder() + .cacheControl(cacheControl) + .executionId(executionId) + .instrumentation(instrumentation) + .graphQLSchema(schema) + .queryStrategy(queryStrategy) + .mutationStrategy(mutationStrategy) + .subscriptionStrategy(subscriptionStrategy) + .root(root) + .graphQLContext(graphQLContext) + .coercedVariables(oldCoercedVariables) + .fragmentsByName([MyFragment: fragment]) + .operationDefinition(operation) + .dataLoaderRegistry(dataLoaderRegistry) + .build() + + when: + def coercedVariables = CoercedVariables.of([var: 'value']) + def executionContext = executionContextOld.transform(builder -> builder + .coercedVariables(coercedVariables)) + + then: + executionContext.cacheControl == cacheControl + executionContext.executionId == executionId + executionContext.instrumentation == instrumentation + executionContext.graphQLSchema == schema + executionContext.queryStrategy == queryStrategy + executionContext.mutationStrategy == mutationStrategy + executionContext.subscriptionStrategy == subscriptionStrategy + executionContext.root == root + executionContext.graphQLContext == graphQLContext + executionContext.coercedVariables == coercedVariables + executionContext.getFragmentsByName() == [MyFragment: fragment] + executionContext.operationDefinition == operation + executionContext.dataLoaderRegistry == dataLoaderRegistry + } } diff --git a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy index fb948b25ba..fcffa7a866 100644 --- a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy @@ -1,7 +1,6 @@ package graphql.execution import graphql.GraphQLContext -import graphql.cachecontrol.CacheControl import graphql.execution.instrumentation.Instrumentation import graphql.language.Document import graphql.language.FragmentDefinition @@ -26,7 +25,6 @@ class ExecutionContextBuilderTest extends Specification { def operation = document.definitions[0] as OperationDefinition def fragment = document.definitions[1] as FragmentDefinition def dataLoaderRegistry = new DataLoaderRegistry() - def cacheControl = CacheControl.newCacheControl() def "builds the correct ExecutionContext"() { when: @@ -44,7 +42,6 @@ class ExecutionContextBuilderTest extends Specification { .fragmentsByName([MyFragment: fragment]) .variables([var: 'value']) // Retain deprecated builder for test coverage .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() then: @@ -61,7 +58,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry - executionContext.cacheControl == cacheControl } def "builds the correct ExecutionContext with coerced variables"() { @@ -83,7 +79,6 @@ class ExecutionContextBuilderTest extends Specification { .fragmentsByName([MyFragment: fragment]) .coercedVariables(coercedVariables) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() then: @@ -99,7 +94,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry - executionContext.cacheControl == cacheControl } def "builds the correct ExecutionContext, if both variables and coercedVariables are set, latest value set takes precedence"() { @@ -121,7 +115,6 @@ class ExecutionContextBuilderTest extends Specification { .fragmentsByName([MyFragment: fragment]) .coercedVariables(coercedVariables) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() then: @@ -137,7 +130,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry - executionContext.cacheControl == cacheControl } def "transform works and copies values with coerced variables"() { @@ -157,7 +149,6 @@ class ExecutionContextBuilderTest extends Specification { .coercedVariables(oldCoercedVariables) .fragmentsByName([MyFragment: fragment]) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() when: @@ -178,7 +169,6 @@ class ExecutionContextBuilderTest extends Specification { executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry - executionContext.cacheControl == cacheControl } def "transform copies values, if both variables and coercedVariables set, latest value set takes precedence"() { @@ -198,7 +188,6 @@ class ExecutionContextBuilderTest extends Specification { .coercedVariables(oldCoercedVariables) .fragmentsByName([MyFragment: fragment]) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() when: @@ -219,6 +208,5 @@ class ExecutionContextBuilderTest extends Specification { executionContext.getFragmentsByName() == [MyFragment: fragment] executionContext.operationDefinition == operation executionContext.dataLoaderRegistry == dataLoaderRegistry - executionContext.cacheControl == cacheControl } } diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index 034a16afab..724cd65516 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -1,7 +1,7 @@ package graphql.schema import graphql.GraphQLContext -import graphql.cachecontrol.CacheControl +import graphql.execution.CoercedVariables import graphql.execution.ExecutionId import graphql.execution.ExecutionStepInfo import graphql.language.FragmentDefinition @@ -30,7 +30,6 @@ class DataFetchingEnvironmentImplTest extends Specification { def fragmentByName = [frag: frag] def variables = [var: "able"] def dataLoaderRegistry = new DataLoaderRegistry().register("dataLoader", dataLoader) - def cacheControl = CacheControl.newCacheControl() def executionContext = newExecutionContextBuilder() .root("root") @@ -39,11 +38,10 @@ class DataFetchingEnvironmentImplTest extends Specification { .executionId(executionId) .operationDefinition(operationDefinition) .document(document) - .variables(variables) + .coercedVariables(CoercedVariables.of(variables)) .graphQLSchema(starWarsSchema) .fragmentsByName(fragmentByName) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .build() @@ -95,7 +93,6 @@ class DataFetchingEnvironmentImplTest extends Specification { .document(document) .variables(variables) .dataLoaderRegistry(dataLoaderRegistry) - .cacheControl(cacheControl) .locale(Locale.CANADA) .localContext("localContext") .build() @@ -121,7 +118,6 @@ class DataFetchingEnvironmentImplTest extends Specification { dfe.getOperationDefinition() == dfeCopy.getOperationDefinition() dfe.getVariables() == dfeCopy.getVariables() dfe.getDataLoader("dataLoader") == dataLoader - dfe.getCacheControl() == cacheControl dfe.getLocale() == dfeCopy.getLocale() dfe.getLocalContext() == dfeCopy.getLocalContext() } From 817a3b72cbe0acb1a3b2af5b867bfb658482c17a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:19:22 +1000 Subject: [PATCH 097/294] Fix up home location of GraphQL.execute tests --- src/test/groovy/graphql/GraphQLTest.groovy | 24 +++++++++++++++++++ .../groovy/graphql/StarWarsQueryTest.groovy | 6 ++++- .../cachecontrol/CacheControlTest.groovy | 2 +- ...tableNormalizedOperationFactoryTest.groovy | 10 +++++--- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 0ae487f569..04358c1bf1 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -1363,4 +1363,28 @@ many lines'''] then: ! er.errors.isEmpty() } + + def "deprecated execute method works"() { + given: + def query = """ + query FetchSomeIDQuery(\$someId: String!) { + human(id: \$someId) { + name + } + } + """ + def params = [ + someId: '1000' + ] + def expected = [ + human: [ + name: 'Luke Skywalker' + ] + ] + when: + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data // Retain deprecated method for test coverage + + then: + result == expected + } } diff --git a/src/test/groovy/graphql/StarWarsQueryTest.groovy b/src/test/groovy/graphql/StarWarsQueryTest.groovy index dcd92f4caa..f1b755e1dd 100644 --- a/src/test/groovy/graphql/StarWarsQueryTest.groovy +++ b/src/test/groovy/graphql/StarWarsQueryTest.groovy @@ -190,7 +190,11 @@ class StarWarsQueryTest extends Specification { ] ] when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data // Retain deprecated method for test coverage + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(null) + .variables(params) + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(executionInput).data then: result == expected diff --git a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy index 8518552e50..6365289004 100644 --- a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy +++ b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy @@ -130,7 +130,7 @@ class CacheControlTest extends Specification { } def "transform works and copies values with cache control"() { - // Retain this ExecutionContext CacheControl test for coverage. + // Retain this ExecutionContext CacheControl test for coverage given: def cacheControl = CacheControl.newCacheControl() def oldCoercedVariables = CoercedVariables.emptyVariables() diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy index 74bf41dd0a..b8189d9b62 100644 --- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy @@ -1,5 +1,6 @@ package graphql.normalized +import graphql.ExecutionInput import graphql.GraphQL import graphql.TestUtil import graphql.execution.CoercedVariables @@ -1314,7 +1315,11 @@ schema { private void assertValidQuery(GraphQLSchema graphQLSchema, String query, Map variables = [:]) { GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build() - assert graphQL.execute(query, null, variables).errors.size() == 0 // Retain deprecated method for test coverage + def executionInput = ExecutionInput.newExecutionInput() + .query(query) + .root(null) + .variables(variables) + assert graphQL.execute(executionInput).errors.size() == 0 } def "normalized arguments"() { @@ -1393,9 +1398,8 @@ schema { assertValidQuery(graphQLSchema, query) def document = TestUtil.parseQuery(query) def dependencyGraph = new ExecutableNormalizedOperationFactory() - def variables = [:] when: - def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.of(variables)) + def tree = dependencyGraph.createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, null, RawVariables.emptyVariables()) then: def topLevelField = tree.getTopLevelFields().get(0) From 06543ba09d453187b36a42b1b1e3f71f9eef4558 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:24:45 +1000 Subject: [PATCH 098/294] Move InputObject test to a better home --- .../schema/GraphQLInputObjectTypeTest.groovy | 20 +++++++++++++++++++ .../validation/ValidationUtilTest.groovy | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy index fe54a55bc9..30e5c7f9c9 100644 --- a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy @@ -1,5 +1,8 @@ package graphql.schema +import graphql.StarWarsSchema +import graphql.language.ObjectValue +import graphql.validation.ValidationUtil import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean @@ -7,6 +10,7 @@ import static graphql.Scalars.GraphQLInt import static graphql.Scalars.GraphQLString import static graphql.schema.GraphQLInputObjectField.newInputObjectField import static graphql.schema.GraphQLInputObjectType.newInputObject +import static graphql.schema.GraphQLNonNull.nonNull class GraphQLInputObjectTypeTest extends Specification { @@ -54,4 +58,20 @@ class GraphQLInputObjectTypeTest extends Specification { transformedInputType.getFieldDefinition("Str").getType() == GraphQLBoolean } + def "deprecated default value builder works"() { + given: + def schema = GraphQLSchema.newSchema().query(StarWarsSchema.queryType).build() + def validationUtil = new ValidationUtil() + def inputObjectType = GraphQLInputObjectType.newInputObject() + .name("inputObjectType") + .field(GraphQLInputObjectField.newInputObjectField() + .name("hello") + .type(nonNull(GraphQLString)) + .defaultValue("default")) // Retain deprecated builder for test coverage + .build() + def objectValue = ObjectValue.newObjectValue() + + expect: + validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema) + } } diff --git a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy index 2bd6858b53..cd97c8727b 100644 --- a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy +++ b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy @@ -190,7 +190,7 @@ class ValidationUtilTest extends Specification { .field(GraphQLInputObjectField.newInputObjectField() .name("hello") .type(nonNull(GraphQLString)) - .defaultValue("default")) // Retain deprecated builder for test coverage + .defaultValueProgrammatic("default")) .build() def objectValue = ObjectValue.newObjectValue() From dbc9896b1bebd2caf9df00599c1ed4fed3a8949b Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 22 Aug 2022 14:55:21 +1000 Subject: [PATCH 099/294] Change Instrumentation production implementations to use non deprecated methods. Cleaned up SimpleInstrumentation --- .../SimpleInstrumentation.java | 48 ++----------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java index ea096c4fcb..9925368fa1 100644 --- a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java @@ -16,7 +16,9 @@ /** * An implementation of {@link graphql.execution.instrumentation.Instrumentation} that does nothing. It can be used - * as a base for derived classes where you only implement the methods you want to + * as a base for derived classes where you only implement the methods you want to. With all the methods in {@link Instrumentation} + * now defaulted (post Java 6) this class is really not needed anymore but has been retained for backwards compatibility + * reasons. */ @PublicApi public class SimpleInstrumentation implements Instrumentation { @@ -26,48 +28,4 @@ public class SimpleInstrumentation implements Instrumentation { */ public static final SimpleInstrumentation INSTANCE = new SimpleInstrumentation(); - @Override - public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } - - @Override - public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } - - @Override - public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } - - @Override - public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { - return new ExecutionStrategyInstrumentationContext() { - @Override - public void onDispatched(CompletableFuture result) { - - } - - @Override - public void onCompleted(ExecutionResult result, Throwable t) { - - } - }; - } - - @Override - public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } - - @Override - public InstrumentationContext beginField(InstrumentationFieldParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } - - @Override - public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - return SimpleInstrumentationContext.noOp(); - } } From e7409ac5263d451417f5651ad5b1dd891440ca29 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 22 Aug 2022 16:44:02 +1000 Subject: [PATCH 100/294] Adding Locale to Coercing and hence ValueResolver (#2912) * A propose shape change on Coercing * deprecation * Added parser new signature * Changed ValueResolver so it took Locale * Changed calls to coerce to use new environment * Changed calls to coerce to NOT use new environment * Updated tests to use Locale * Formatting of ValuesResolver * Move to graphql context for coercing and also got the parsing error messages in place * Merged in master * Added Locale to Coercing and everywhere that calls it * now using Locale in the standard scalars * Break out legacy ValuesResolver code * Some renaming of methods * Refactoring ValueResolver so that conversion methods are in another class * DirectivesResolver should take context and locale from input * Reverting Parser changes - they can go into their own PR * PR feedback * PR feedback- fixed tests --- src/main/java/graphql/ExecutionInput.java | 2 +- src/main/java/graphql/GraphQLContext.java | 7 + .../analysis/NodeVisitorWithTypeTracking.java | 9 +- .../java/graphql/analysis/QueryTraverser.java | 4 +- .../graphql/execution/ConditionalNodes.java | 4 +- .../java/graphql/execution/Execution.java | 2 +- .../execution/ExecutionStepInfoFactory.java | 8 +- .../graphql/execution/ExecutionStrategy.java | 16 +- .../graphql/execution/ValuesResolver.java | 735 +++--------------- .../execution/ValuesResolverConversion.java | 568 ++++++++++++++ .../execution/ValuesResolverLegacy.java | 167 ++++ .../directives/DirectivesResolver.java | 16 +- .../QueryAppliedDirectiveArgument.java | 4 +- .../directives/QueryDirectivesImpl.java | 10 +- src/main/java/graphql/i18n/I18n.java | 31 +- .../graphql/introspection/Introspection.java | 15 +- .../IntrospectionWithDirectivesSupport.java | 2 +- .../ExecutableNormalizedOperationFactory.java | 24 +- .../FieldCollectorNormalizedQueryParams.java | 41 +- src/main/java/graphql/parser/Parser.java | 46 +- .../graphql/parser/ParserEnvironment.java | 87 +++ .../java/graphql/scalar/CoercingUtil.java | 13 +- .../scalar/GraphqlBooleanCoercing.java | 77 +- .../graphql/scalar/GraphqlFloatCoercing.java | 78 +- .../graphql/scalar/GraphqlIDCoercing.java | 75 +- .../graphql/scalar/GraphqlIntCoercing.java | 79 +- .../graphql/scalar/GraphqlStringCoercing.java | 75 +- src/main/java/graphql/schema/Coercing.java | 108 ++- .../GraphQLAppliedDirectiveArgument.java | 4 +- .../java/graphql/schema/GraphQLArgument.java | 6 +- .../java/graphql/schema/GraphQLEnumType.java | 58 +- .../schema/GraphQLInputObjectField.java | 4 +- .../idl/ArgValueOfAllowedTypeChecker.java | 5 +- .../graphql/schema/idl/SchemaPrinter.java | 4 +- .../AppliedDirectiveArgumentsAreValid.java | 11 +- .../validation/DefaultValuesAreValid.java | 18 +- .../validation/TypesImplementInterfaces.java | 6 +- src/main/java/graphql/util/Anonymizer.java | 13 +- .../graphql/validation/ValidationContext.java | 8 + .../graphql/validation/ValidationUtil.java | 33 +- .../rules/ArgumentsOfCorrectType.java | 4 +- .../VariableDefaultValuesOfCorrectType.java | 4 +- .../validation/rules/VariableTypesMatch.java | 4 +- src/main/resources/i18n/Scalars.properties | 29 + src/test/groovy/graphql/GraphQLTest.groovy | 2 +- src/test/groovy/graphql/Issue2001.groovy | 2 +- src/test/groovy/graphql/Issue739.groovy | 2 +- .../AsyncExecutionStrategyTest.groovy | 17 +- .../AsyncSerialExecutionStrategyTest.groovy | 6 +- .../execution/ValuesResolverTest.groovy | 59 +- .../execution/ValuesResolverTestLegacy.groovy | 79 +- .../directives/QueryDirectivesImplTest.groovy | 3 +- .../groovy/graphql/parser/ParserTest.groovy | 18 + .../SchemaGeneratorDirectiveHelperTest.groovy | 3 +- .../validation/ValidationUtilTest.groovy | 39 +- .../rules/ArgumentsOfCorrectTypeTest.groovy | 7 +- ...iableDefaultValuesOfCorrectTypeTest.groovy | 7 + 57 files changed, 1840 insertions(+), 918 deletions(-) create mode 100644 src/main/java/graphql/execution/ValuesResolverConversion.java create mode 100644 src/main/java/graphql/execution/ValuesResolverLegacy.java create mode 100644 src/main/java/graphql/parser/ParserEnvironment.java create mode 100644 src/main/resources/i18n/Scalars.properties diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 376fa82249..4cace2b7b5 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -44,7 +44,7 @@ private ExecutionInput(Builder builder) { this.dataLoaderRegistry = builder.dataLoaderRegistry; this.cacheControl = builder.cacheControl; this.executionId = builder.executionId; - this.locale = builder.locale; + this.locale = builder.locale != null ? builder.locale : Locale.getDefault(); // always have a locale in place this.localContext = builder.localContext; this.extensions = builder.extensions; } diff --git a/src/main/java/graphql/GraphQLContext.java b/src/main/java/graphql/GraphQLContext.java index 03c2e89d41..081c17725f 100644 --- a/src/main/java/graphql/GraphQLContext.java +++ b/src/main/java/graphql/GraphQLContext.java @@ -224,6 +224,13 @@ public static GraphQLContext of(Consumer contextBuilderC return of(builder.map); } + /** + * @return a new and empty graphql context object + */ + public static GraphQLContext getDefault() { + return GraphQLContext.newContext().build(); + } + /** * Creates a new GraphqlContext builder * diff --git a/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java b/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java index 93d2371daa..7b65c8e9ea 100644 --- a/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java +++ b/src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java @@ -1,5 +1,6 @@ package graphql.analysis; +import graphql.GraphQLContext; import graphql.Internal; import graphql.execution.CoercedVariables; import graphql.execution.ConditionalNodes; @@ -29,6 +30,7 @@ import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import java.util.Locale; import java.util.Map; import static graphql.Assert.assertNotNull; @@ -151,7 +153,12 @@ public TraversalControl visitField(Field field, TraverserContext context) boolean isTypeNameIntrospectionField = fieldDefinition == schema.getIntrospectionTypenameFieldDefinition(); GraphQLFieldsContainer fieldsContainer = !isTypeNameIntrospectionField ? (GraphQLFieldsContainer) unwrapAll(parentEnv.getOutputType()) : null; GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry(); - Map argumentValues = ValuesResolver.getArgumentValues(codeRegistry, fieldDefinition.getArguments(), field.getArguments(), CoercedVariables.of(variables)); + Map argumentValues = ValuesResolver.getArgumentValues(codeRegistry, + fieldDefinition.getArguments(), + field.getArguments(), + CoercedVariables.of(variables), + GraphQLContext.getDefault(), + Locale.getDefault()); QueryVisitorFieldEnvironment environment = new QueryVisitorFieldEnvironmentImpl(isTypeNameIntrospectionField, field, fieldDefinition, diff --git a/src/main/java/graphql/analysis/QueryTraverser.java b/src/main/java/graphql/analysis/QueryTraverser.java index 69f1a09b53..14d873f599 100644 --- a/src/main/java/graphql/analysis/QueryTraverser.java +++ b/src/main/java/graphql/analysis/QueryTraverser.java @@ -1,5 +1,6 @@ package graphql.analysis; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.execution.CoercedVariables; import graphql.execution.RawVariables; @@ -20,6 +21,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static graphql.Assert.assertNotNull; @@ -70,7 +72,7 @@ private QueryTraverser(GraphQLSchema schema, this.fragmentsByName = getOperationResult.fragmentsByName; this.roots = singletonList(getOperationResult.operationDefinition); this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition); - this.coercedVariables = ValuesResolver.coerceVariableValues(schema, variableDefinitions, rawVariables); + this.coercedVariables = ValuesResolver.coerceVariableValues(schema, variableDefinitions, rawVariables, GraphQLContext.getDefault(), Locale.getDefault()); } private QueryTraverser(GraphQLSchema schema, diff --git a/src/main/java/graphql/execution/ConditionalNodes.java b/src/main/java/graphql/execution/ConditionalNodes.java index 45b61986ba..a9e3ca733e 100644 --- a/src/main/java/graphql/execution/ConditionalNodes.java +++ b/src/main/java/graphql/execution/ConditionalNodes.java @@ -1,11 +1,13 @@ package graphql.execution; import graphql.Assert; +import graphql.GraphQLContext; import graphql.Internal; import graphql.language.Directive; import graphql.language.NodeUtil; import java.util.List; +import java.util.Locale; import java.util.Map; import static graphql.Directives.IncludeDirective; @@ -30,7 +32,7 @@ public boolean shouldInclude(Map variables, List dire private boolean getDirectiveResult(Map variables, List directives, String directiveName, boolean defaultValue) { Directive foundDirective = NodeUtil.findNodeByName(directives, directiveName); if (foundDirective != null) { - Map argumentValues = ValuesResolver.getArgumentValues(SkipDirective.getArguments(), foundDirective.getArguments(), CoercedVariables.of(variables)); + Map argumentValues = ValuesResolver.getArgumentValues(SkipDirective.getArguments(), foundDirective.getArguments(), CoercedVariables.of(variables), GraphQLContext.getDefault(), Locale.getDefault()); Object flag = argumentValues.get("if"); Assert.assertTrue(flag instanceof Boolean, () -> String.format("The '%s' directive MUST have a value for the 'if' argument", directiveName)); return (Boolean) flag; diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index fa69bcbcce..f7e97ed774 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -63,7 +63,7 @@ public CompletableFuture execute(Document document, GraphQLSche CoercedVariables coercedVariables; try { - coercedVariables = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, inputVariables); + coercedVariables = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, inputVariables, executionInput.getGraphQLContext(), executionInput.getLocale()); } catch (RuntimeException rte) { if (rte instanceof GraphQLError) { return completedFuture(new ExecutionResultImpl((GraphQLError) rte)); diff --git a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java index 9042c90659..3338961388 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java +++ b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java @@ -23,7 +23,13 @@ public ExecutionStepInfo newExecutionStepInfoForSubField(ExecutionContext execut GraphQLOutputType fieldType = fieldDefinition.getType(); List fieldArgs = mergedField.getArguments(); GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); - Supplier> argumentValues = FpKit.intraThreadMemoize(() -> ValuesResolver.getArgumentValues(codeRegistry, fieldDefinition.getArguments(), fieldArgs, executionContext.getCoercedVariables())); + Supplier> argumentValuesSupplier = () -> ValuesResolver.getArgumentValues(codeRegistry, + fieldDefinition.getArguments(), + fieldArgs, + executionContext.getCoercedVariables(), + executionContext.getGraphQLContext(), + executionContext.getLocale()); + Supplier> argumentValues = FpKit.intraThreadMemoize(argumentValuesSupplier); ResultPath newPath = parentInfo.getPath().segment(mergedField.getResultKey()); diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index bf2de70f22..4008b6e00a 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -250,7 +250,11 @@ protected CompletableFuture fetchField(ExecutionContext executionC // DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalizedFieldSupplier); - QueryDirectives queryDirectives = new QueryDirectivesImpl(field, executionContext.getGraphQLSchema(), executionContext.getVariables()); + QueryDirectives queryDirectives = new QueryDirectivesImpl(field, + executionContext.getGraphQLSchema(), + executionContext.getCoercedVariables().toMap(), + executionContext.getGraphQLContext(), + executionContext.getLocale()); DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext) @@ -590,7 +594,7 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext, protected CompletableFuture completeValueForScalar(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLScalarType scalarType, Object result) { Object serialized; try { - serialized = scalarType.getCoercing().serialize(result); + serialized = scalarType.getCoercing().serialize(result, executionContext.getGraphQLContext(), executionContext.getLocale()); } catch (CoercingSerializeException e) { serialized = handleCoercionProblem(executionContext, parameters, e); } @@ -820,7 +824,13 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo if (!fieldArgDefs.isEmpty()) { List fieldArgs = field.getArguments(); GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); - argumentValues = FpKit.intraThreadMemoize(() -> ValuesResolver.getArgumentValues(codeRegistry, fieldArgDefs, fieldArgs, executionContext.getCoercedVariables())); + Supplier> argValuesSupplier = () -> ValuesResolver.getArgumentValues(codeRegistry, + fieldArgDefs, + fieldArgs, + executionContext.getCoercedVariables(), + executionContext.getGraphQLContext(), + executionContext.getLocale()); + argumentValues = FpKit.intraThreadMemoize(argValuesSupplier); } diff --git a/src/main/java/graphql/execution/ValuesResolver.java b/src/main/java/graphql/execution/ValuesResolver.java index fb93b51ae4..78b2e79d83 100644 --- a/src/main/java/graphql/execution/ValuesResolver.java +++ b/src/main/java/graphql/execution/ValuesResolver.java @@ -1,22 +1,14 @@ package graphql.execution; -import com.google.common.collect.ImmutableList; -import graphql.AssertException; +import graphql.GraphQLContext; import graphql.Internal; -import graphql.Scalars; -import graphql.VisibleForTesting; import graphql.collect.ImmutableKit; import graphql.language.Argument; import graphql.language.ArrayValue; -import graphql.language.BooleanValue; -import graphql.language.EnumValue; -import graphql.language.FloatValue; -import graphql.language.IntValue; import graphql.language.NullValue; import graphql.language.ObjectField; import graphql.language.ObjectValue; -import graphql.language.StringValue; import graphql.language.Value; import graphql.language.VariableDefinition; import graphql.language.VariableReference; @@ -26,45 +18,33 @@ import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLEnumType; -import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLList; -import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeUtil; import graphql.schema.InputValueWithState; -import graphql.schema.PropertyDataFetcherHelper; -import graphql.schema.visibility.DefaultGraphqlFieldVisibility; import graphql.schema.visibility.GraphqlFieldVisibility; -import graphql.util.FpKit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.math.BigDecimal; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; -import static graphql.collect.ImmutableKit.emptyList; -import static graphql.collect.ImmutableKit.map; import static graphql.execution.ValuesResolver.ValueMode.NORMALIZED; -import static graphql.language.NullValue.newNullValue; -import static graphql.language.ObjectField.newObjectField; +import static graphql.execution.ValuesResolverConversion.externalValueToInternalValueImpl; import static graphql.schema.GraphQLTypeUtil.isList; import static graphql.schema.GraphQLTypeUtil.isNonNull; import static graphql.schema.GraphQLTypeUtil.simplePrint; -import static graphql.schema.GraphQLTypeUtil.unwrapNonNull; import static graphql.schema.GraphQLTypeUtil.unwrapOne; import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY; -import static java.util.stream.Collectors.toList; @SuppressWarnings("rawtypes") @Internal @@ -87,28 +67,37 @@ public enum ValueMode { * @param schema the schema * @param variableDefinitions the variable definitions * @param rawVariables the supplied variables + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use * * @return coerced variable values as a map */ public static CoercedVariables coerceVariableValues(GraphQLSchema schema, - List variableDefinitions, - RawVariables rawVariables) throws CoercingParseValueException, NonNullableValueCoercedAsNullException { + List variableDefinitions, + RawVariables rawVariables, + GraphQLContext graphqlContext, + Locale locale) throws CoercingParseValueException, NonNullableValueCoercedAsNullException { - return externalValueToInternalValueForVariables(schema, variableDefinitions, rawVariables); + return ValuesResolverConversion.externalValueToInternalValueForVariables(schema, variableDefinitions, rawVariables, graphqlContext, locale); } + /** * Normalized variables values are Literals with type information. No validation here! * * @param schema the schema to use * @param variableDefinitions the list of variable definitions * @param rawVariables the raw variables + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use * * @return a map of the normalised values */ public static Map getNormalizedVariableValues(GraphQLSchema schema, - List variableDefinitions, - RawVariables rawVariables) { + List variableDefinitions, + RawVariables rawVariables, + GraphQLContext graphqlContext, + Locale locale) { GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility(); Map result = new LinkedHashMap<>(); for (VariableDefinition variableDefinition : variableDefinitions) { @@ -127,7 +116,7 @@ public static Map getNormalizedVariableValues(Grap if (value == null) { result.put(variableName, new NormalizedInputValue(simplePrint(variableType), null)); } else { - Object literal = externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) variableType, NORMALIZED); + Object literal = ValuesResolverConversion.externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) variableType, NORMALIZED, graphqlContext, locale); result.put(variableName, new NormalizedInputValue(simplePrint(variableType), literal)); } } @@ -137,19 +126,24 @@ public static Map getNormalizedVariableValues(Grap } + /** * This is not used for validation: the argument literals are all validated and the variables are validated (when coerced) * * @param argumentTypes the list of argument types * @param arguments the AST arguments * @param coercedVariables the coerced variables + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use * * @return a map of named argument values */ public static Map getArgumentValues(List argumentTypes, - List arguments, - CoercedVariables coercedVariables) { - return getArgumentValuesImpl(DEFAULT_FIELD_VISIBILITY, argumentTypes, arguments, coercedVariables); + List arguments, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { + return getArgumentValuesImpl(DEFAULT_FIELD_VISIBILITY, argumentTypes, arguments, coercedVariables, graphqlContext, locale); } /** @@ -162,8 +156,8 @@ public static Map getArgumentValues(List argume * @return a map of named normalised values */ public static Map getNormalizedArgumentValues(List argumentTypes, - List arguments, - Map normalizedVariables) { + List arguments, + Map normalizedVariables) { if (argumentTypes.isEmpty()) { return ImmutableKit.emptyMap(); } @@ -190,47 +184,48 @@ public static Map getNormalizedArgumentValues(List } public static Map getArgumentValues(GraphQLCodeRegistry codeRegistry, - List argumentTypes, - List arguments, - CoercedVariables coercedVariables) { - return getArgumentValuesImpl(codeRegistry.getFieldVisibility(), argumentTypes, arguments, coercedVariables); - } - - public static Value valueToLiteral(InputValueWithState inputValueWithState, GraphQLType type) { - return valueToLiteral(DEFAULT_FIELD_VISIBILITY, inputValueWithState, type); + List argumentTypes, + List arguments, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { + return getArgumentValuesImpl(codeRegistry.getFieldVisibility(), argumentTypes, arguments, coercedVariables, graphqlContext, locale); } /** * Takes a value which can be in different states (internal, literal, external value) and converts into Literal - * + *

* This assumes the value is valid! * * @param fieldVisibility the field visibility to use * @param inputValueWithState the input value * @param type the type of input value + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use * * @return a value converted to a literal */ - public static Value valueToLiteral(@NotNull GraphqlFieldVisibility fieldVisibility, @NotNull InputValueWithState inputValueWithState, @NotNull GraphQLType type) { - return (Value) valueToLiteral(fieldVisibility, inputValueWithState, type, ValueMode.LITERAL); + public static Value valueToLiteral(@NotNull GraphqlFieldVisibility fieldVisibility, + @NotNull InputValueWithState inputValueWithState, + @NotNull GraphQLType type, + GraphQLContext graphqlContext, + Locale locale) { + return (Value) ValuesResolverConversion.valueToLiteralImpl(fieldVisibility, inputValueWithState, type, ValueMode.LITERAL, graphqlContext, locale); } - private static Object valueToLiteral(GraphqlFieldVisibility fieldVisibility, InputValueWithState inputValueWithState, GraphQLType type, ValueMode valueMode) { - if (inputValueWithState.isInternal()) { - if (valueMode == NORMALIZED) { - return assertShouldNeverHappen("can't infer normalized structure"); - } - return valueToLiteralLegacy(inputValueWithState.getValue(), type); - } - if (inputValueWithState.isLiteral()) { - return inputValueWithState.getValue(); - } - if (inputValueWithState.isExternal()) { - return ValuesResolver.externalValueToLiteral(fieldVisibility, inputValueWithState.getValue(), (GraphQLInputType) type, valueMode); - } - return assertShouldNeverHappen("unexpected value state " + inputValueWithState); + public static Value valueToLiteral(@NotNull InputValueWithState inputValueWithState, + @NotNull GraphQLType type, + GraphQLContext graphqlContext, + Locale locale) { + return (Value) ValuesResolverConversion.valueToLiteralImpl(DEFAULT_FIELD_VISIBILITY, inputValueWithState, type, ValueMode.LITERAL, graphqlContext, locale); } + public static Object valueToInternalValue(InputValueWithState inputValueWithState, + GraphQLType type, + GraphQLContext graphqlContext, + Locale locale) throws CoercingParseValueException, CoercingParseLiteralException { + return ValuesResolverConversion.valueToInternalValueImpl(inputValueWithState, type, graphqlContext, locale); + } /** * Converts an external value to an internal value @@ -238,200 +233,39 @@ private static Object valueToLiteral(GraphqlFieldVisibility fieldVisibility, Inp * @param fieldVisibility the field visibility to use * @param externalValue the input external value * @param type the type of input value + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use * * @return a value converted to an internal value */ - public static Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility, Object externalValue, GraphQLInputType type) { - return ValuesResolver.externalValueToInternalValue(fieldVisibility, type, externalValue); - } - - public static Object valueToInternalValue(InputValueWithState inputValueWithState, GraphQLType type) throws CoercingParseValueException, CoercingParseLiteralException { - DefaultGraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY; - if (inputValueWithState.isInternal()) { - return inputValueWithState.getValue(); - } - if (inputValueWithState.isLiteral()) { - return ValuesResolver.literalToInternalValue(fieldVisibility, type, (Value) inputValueWithState.getValue(), CoercedVariables.emptyVariables()); - } - if (inputValueWithState.isExternal()) { - return ValuesResolver.externalValueToInternalValue(fieldVisibility, type, inputValueWithState.getValue()); - } - return assertShouldNeverHappen("unexpected value state " + inputValueWithState); + public static Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility, + Object externalValue, + GraphQLInputType type, + GraphQLContext graphqlContext, + Locale locale) { + return externalValueToInternalValueImpl(fieldVisibility, type, externalValue, graphqlContext, locale); } @Nullable @SuppressWarnings("unchecked") - public static T getInputValueImpl(GraphQLInputType inputType, InputValueWithState inputValue) { + public static T getInputValueImpl(GraphQLInputType inputType, + InputValueWithState inputValue, + GraphQLContext graphqlContext, + Locale locale) { if (inputValue.isNotSet()) { return null; } - return (T) valueToInternalValue(inputValue, inputType); - } - - /** - * No validation: the external value is assumed to be valid. - */ - private static Object externalValueToLiteral(GraphqlFieldVisibility fieldVisibility, - @Nullable Object value, - GraphQLInputType type, - ValueMode valueMode - ) { - if (value == null) { - return newNullValue().build(); - } - if (GraphQLTypeUtil.isNonNull(type)) { - return externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) unwrapNonNull(type), valueMode); - } - if (type instanceof GraphQLScalarType) { - return externalValueToLiteralForScalar((GraphQLScalarType) type, value); - } else if (type instanceof GraphQLEnumType) { - return externalValueToLiteralForEnum((GraphQLEnumType) type, value); - } else if (type instanceof GraphQLList) { - return externalValueToLiteralForList(fieldVisibility, (GraphQLList) type, value, valueMode); - } else if (type instanceof GraphQLInputObjectType) { - return externalValueToLiteralForObject(fieldVisibility, (GraphQLInputObjectType) type, value, valueMode); - } else { - return assertShouldNeverHappen("unexpected type %s", type); - } - } - - /** - * No validation - */ - private static Value externalValueToLiteralForScalar(GraphQLScalarType scalarType, Object value) { - return scalarType.getCoercing().valueToLiteral(value); - - } - - /** - * No validation - */ - private static Value externalValueToLiteralForEnum(GraphQLEnumType enumType, Object value) { - return enumType.valueToLiteral(value); - } - - /** - * No validation - */ - @SuppressWarnings("unchecked") - private static Object externalValueToLiteralForList(GraphqlFieldVisibility fieldVisibility, GraphQLList listType, Object value, ValueMode valueMode) { - GraphQLInputType wrappedType = (GraphQLInputType) listType.getWrappedType(); - List result = FpKit.toListOrSingletonList(value) - .stream() - .map(val -> externalValueToLiteral(fieldVisibility, val, wrappedType, valueMode)) - .collect(toList()); - if (valueMode == NORMALIZED) { - return result; - } else { - return ArrayValue.newArrayValue().values(result).build(); - } - } - - /** - * No validation - */ - @SuppressWarnings("unchecked") - private static Object externalValueToLiteralForObject(GraphqlFieldVisibility fieldVisibility, - GraphQLInputObjectType inputObjectType, - Object inputValue, - ValueMode valueMode) { - assertTrue(inputValue instanceof Map, () -> "Expect Map as input"); - Map inputMap = (Map) inputValue; - List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType); - - Map normalizedResult = new LinkedHashMap<>(); - ImmutableList.Builder objectFields = ImmutableList.builder(); - for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) { - GraphQLInputType fieldType = inputFieldDefinition.getType(); - String fieldName = inputFieldDefinition.getName(); - boolean hasValue = inputMap.containsKey(fieldName); - Object fieldValue = inputMap.getOrDefault(fieldName, null); - if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { - //TODO: consider valueMode - Object defaultValueLiteral = valueToLiteral(fieldVisibility, inputFieldDefinition.getInputFieldDefaultValue(), fieldType); - if (valueMode == ValueMode.LITERAL) { - normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), defaultValueLiteral)); - } else { - objectFields.add(newObjectField().name(fieldName).value((Value) defaultValueLiteral).build()); - } - } else if (hasValue) { - if (fieldValue == null) { - if (valueMode == NORMALIZED) { - normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), null)); - } else { - objectFields.add(newObjectField().name(fieldName).value(newNullValue().build()).build()); - } - } else { - Object literal = externalValueToLiteral(fieldVisibility, - fieldValue, - fieldType, - valueMode); - if (valueMode == NORMALIZED) { - normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), literal)); - } else { - objectFields.add(newObjectField().name(fieldName).value((Value) literal).build()); - } - } - } - } - if (valueMode == NORMALIZED) { - return normalizedResult; - } - return ObjectValue.newObjectValue().objectFields(objectFields.build()).build(); - } - - - /** - * performs validation too - */ - private static CoercedVariables externalValueToInternalValueForVariables(GraphQLSchema schema, - List variableDefinitions, - RawVariables rawVariables) { - GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility(); - Map coercedValues = new LinkedHashMap<>(); - for (VariableDefinition variableDefinition : variableDefinitions) { - try { - String variableName = variableDefinition.getName(); - GraphQLType variableType = TypeFromAST.getTypeFromAST(schema, variableDefinition.getType()); - assertTrue(variableType instanceof GraphQLInputType); - // can be NullValue - Value defaultValue = variableDefinition.getDefaultValue(); - boolean hasValue = rawVariables.containsKey(variableName); - Object value = rawVariables.get(variableName); - if (!hasValue && defaultValue != null) { - Object coercedDefaultValue = literalToInternalValue(fieldVisibility, variableType, defaultValue, CoercedVariables.emptyVariables()); - coercedValues.put(variableName, coercedDefaultValue); - } else if (isNonNull(variableType) && (!hasValue || value == null)) { - throw new NonNullableValueCoercedAsNullException(variableDefinition, variableType); - } else if (hasValue) { - if (value == null) { - coercedValues.put(variableName, null); - } else { - Object coercedValue = externalValueToInternalValue(fieldVisibility, variableType, value); - coercedValues.put(variableName, coercedValue); - } - } - } catch (CoercingParseValueException e) { - throw CoercingParseValueException.newCoercingParseValueException() - .message(String.format("Variable '%s' has an invalid value: %s", variableDefinition.getName(), e.getMessage())) - .extensions(e.getExtensions()) - .cause(e.getCause()) - .sourceLocation(variableDefinition.getSourceLocation()) - .build(); - } catch (NonNullableValueCoercedAsNullException e) { - throw new NonNullableValueCoercedAsNullException(variableDefinition, e.getMessage()); - } - } - - return CoercedVariables.of(coercedValues); + return (T) valueToInternalValue(inputValue, inputType, graphqlContext, locale); } private static Map getArgumentValuesImpl(GraphqlFieldVisibility fieldVisibility, - List argumentTypes, - List arguments, - CoercedVariables coercedVariables) { + List argumentTypes, + List arguments, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { if (argumentTypes.isEmpty()) { return ImmutableKit.emptyMap(); } @@ -454,20 +288,21 @@ private static Map getArgumentValuesImpl(GraphqlFieldVisibility value = argumentValue; } if (!hasValue && argumentDefinition.hasSetDefaultValue()) { - Object coercedDefaultValue = defaultValueToInternalValue( + Object coercedDefaultValue = ValuesResolverConversion.defaultValueToInternalValue( fieldVisibility, defaultValue, - argumentType); + argumentType, + graphqlContext, locale); coercedValues.put(argumentName, coercedDefaultValue); - } else if (isNonNull(argumentType) && (!hasValue || isNullValue(value))) { + } else if (isNonNull(argumentType) && (!hasValue || ValuesResolverConversion.isNullValue(value))) { throw new NonNullableValueCoercedAsNullException(argumentDefinition); } else if (hasValue) { - if (isNullValue(value)) { + if (ValuesResolverConversion.isNullValue(value)) { coercedValues.put(argumentName, value); } else if (argumentValue instanceof VariableReference) { coercedValues.put(argumentName, value); } else { - value = literalToInternalValue(fieldVisibility, argumentType, argument.getValue(), coercedVariables); + value = ValuesResolverConversion.literalToInternalValue(fieldVisibility, argumentType, argument.getValue(), coercedVariables, graphqlContext, locale); coercedValues.put(argumentName, value); } } @@ -485,124 +320,10 @@ private static Map argumentMap(List arguments) { } - /** - * Performs validation too - */ - @SuppressWarnings("unchecked") - private static Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility, - GraphQLType graphQLType, - Object value) throws NonNullableValueCoercedAsNullException, CoercingParseValueException { - if (isNonNull(graphQLType)) { - Object returnValue = - externalValueToInternalValue(fieldVisibility, unwrapOne(graphQLType), value); - if (returnValue == null) { - throw new NonNullableValueCoercedAsNullException(graphQLType); - } - return returnValue; - } - - if (value == null) { - return null; - } - - if (graphQLType instanceof GraphQLScalarType) { - return externalValueToInternalValueForScalar((GraphQLScalarType) graphQLType, value); - } else if (graphQLType instanceof GraphQLEnumType) { - return externalValueToInternalValueForEnum((GraphQLEnumType) graphQLType, value); - } else if (graphQLType instanceof GraphQLList) { - return externalValueToInternalValueForList(fieldVisibility, (GraphQLList) graphQLType, value); - } else if (graphQLType instanceof GraphQLInputObjectType) { - if (value instanceof Map) { - return externalValueToInternalValueForObject(fieldVisibility, (GraphQLInputObjectType) graphQLType, (Map) value); - } else { - throw CoercingParseValueException.newCoercingParseValueException() - .message("Expected type 'Map' but was '" + value.getClass().getSimpleName() + - "'. Variables for input objects must be an instance of type 'Map'.") - .build(); - } - } else { - return assertShouldNeverHappen("unhandled type %s", graphQLType); - } - } - - /** - * performs validation - */ - private static Object externalValueToInternalValueForObject(GraphqlFieldVisibility fieldVisibility, - GraphQLInputObjectType inputObjectType, - Map inputMap - ) throws NonNullableValueCoercedAsNullException, CoercingParseValueException { - List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType); - List fieldNames = map(fieldDefinitions, GraphQLInputObjectField::getName); - for (String providedFieldName : inputMap.keySet()) { - if (!fieldNames.contains(providedFieldName)) { - throw new InputMapDefinesTooManyFieldsException(inputObjectType, providedFieldName); - } - } - - Map coercedValues = new LinkedHashMap<>(); - - for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) { - GraphQLInputType fieldType = inputFieldDefinition.getType(); - String fieldName = inputFieldDefinition.getName(); - InputValueWithState defaultValue = inputFieldDefinition.getInputFieldDefaultValue(); - boolean hasValue = inputMap.containsKey(fieldName); - Object value; - Object fieldValue = inputMap.getOrDefault(fieldName, null); - value = fieldValue; - if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { - Object coercedDefaultValue = defaultValueToInternalValue(fieldVisibility, - defaultValue, - fieldType); - coercedValues.put(fieldName, coercedDefaultValue); - } else if (isNonNull(fieldType) && (!hasValue || value == null)) { - throw new NonNullableValueCoercedAsNullException(fieldName, emptyList(), fieldType); - } else if (hasValue) { - if (value == null) { - coercedValues.put(fieldName, null); - } else { - value = externalValueToInternalValue(fieldVisibility, - fieldType, value); - coercedValues.put(fieldName, value); - } - } - } - return coercedValues; - } - - /** - * including validation - */ - private static Object externalValueToInternalValueForScalar(GraphQLScalarType graphQLScalarType, Object value) throws CoercingParseValueException { - return graphQLScalarType.getCoercing().parseValue(value); - } - - /** - * including validation - */ - private static Object externalValueToInternalValueForEnum(GraphQLEnumType graphQLEnumType, Object value) throws CoercingParseValueException { - return graphQLEnumType.parseValue(value); - } - - /** - * including validation - */ - private static List externalValueToInternalValueForList(GraphqlFieldVisibility fieldVisibility, - GraphQLList graphQLList, - Object value - ) throws CoercingParseValueException, NonNullableValueCoercedAsNullException { - - GraphQLType wrappedType = graphQLList.getWrappedType(); - return FpKit.toListOrSingletonList(value) - .stream() - .map(val -> externalValueToInternalValue(fieldVisibility, wrappedType, val)) - .collect(toList()); - } - public static Object literalToNormalizedValue(GraphqlFieldVisibility fieldVisibility, - GraphQLType type, - Value inputValue, - Map normalizedVariables + GraphQLType type, + Value inputValue, + Map normalizedVariables ) { if (inputValue instanceof VariableReference) { String varName = ((VariableReference) inputValue).getName(); @@ -631,9 +352,9 @@ public static Object literalToNormalizedValue(GraphqlFieldVisibility fieldVisibi } private static Object literalToNormalizedValueForInputObject(GraphqlFieldVisibility fieldVisibility, - GraphQLInputObjectType type, - ObjectValue inputObjectLiteral, - Map normalizedVariables) { + GraphQLInputObjectType type, + ObjectValue inputObjectLiteral, + Map normalizedVariables) { Map result = new LinkedHashMap<>(); for (ObjectField field : inputObjectLiteral.getObjectFields()) { @@ -650,9 +371,9 @@ private static Object literalToNormalizedValueForInputObject(GraphqlFieldVisibil } private static List literalToNormalizedValueForList(GraphqlFieldVisibility fieldVisibility, - GraphQLList type, - Value value, - Map normalizedVariables) { + GraphQLList type, + Value value, + Map normalizedVariables) { if (value instanceof ArrayValue) { List result = new ArrayList<>(); for (Value valueInArray : ((ArrayValue) value).getValues()) { @@ -665,288 +386,6 @@ private static List literalToNormalizedValueForList(GraphqlFieldVisibili } - /** - * No validation (it was checked before via ArgumentsOfCorrectType and VariableDefaultValuesOfCorrectType) - * - * @param fieldVisibility the field visibility - * @param type the type of the input value - * @param inputValue the AST literal to be changed - * @param coercedVariables the coerced variable values - * - * @return literal converted to an internal value - */ - public static Object literalToInternalValue(GraphqlFieldVisibility fieldVisibility, - GraphQLType type, - Value inputValue, - CoercedVariables coercedVariables) { - - if (inputValue instanceof VariableReference) { - return coercedVariables.get(((VariableReference) inputValue).getName()); - } - if (inputValue instanceof NullValue) { - return null; - } - if (type instanceof GraphQLScalarType) { - return literalToInternalValueForScalar(inputValue, (GraphQLScalarType) type, coercedVariables); - } - if (isNonNull(type)) { - return literalToInternalValue(fieldVisibility, unwrapOne(type), inputValue, coercedVariables); - } - if (type instanceof GraphQLInputObjectType) { - return literalToInternalValueForInputObject(fieldVisibility, (GraphQLInputObjectType) type, (ObjectValue) inputValue, coercedVariables); - } - if (type instanceof GraphQLEnumType) { - return ((GraphQLEnumType) type).parseLiteral(inputValue); - } - if (isList(type)) { - return literalToInternalValueForList(fieldVisibility, (GraphQLList) type, inputValue, coercedVariables); - } - return null; - } - - /** - * no validation - */ - private static Object literalToInternalValueForScalar(Value inputValue, GraphQLScalarType scalarType, CoercedVariables coercedVariables) { - // the CoercingParseLiteralException exception that could happen here has been validated earlier via ValidationUtil - return scalarType.getCoercing().parseLiteral(inputValue, coercedVariables.toMap()); - } - - /** - * no validation - */ - private static Object literalToInternalValueForList(GraphqlFieldVisibility fieldVisibility, - GraphQLList graphQLList, - Value value, - CoercedVariables coercedVariables) { - - if (value instanceof ArrayValue) { - ArrayValue arrayValue = (ArrayValue) value; - List result = new ArrayList<>(); - for (Value singleValue : arrayValue.getValues()) { - result.add(literalToInternalValue(fieldVisibility, graphQLList.getWrappedType(), singleValue, coercedVariables)); - } - return result; - } else { - return Collections.singletonList( - literalToInternalValue(fieldVisibility, - graphQLList.getWrappedType(), - value, - coercedVariables)); - } - } - - /** - * no validation - */ - private static Object literalToInternalValueForInputObject(GraphqlFieldVisibility fieldVisibility, - GraphQLInputObjectType type, - ObjectValue inputValue, - CoercedVariables coercedVariables) { - Map coercedValues = new LinkedHashMap<>(); - - Map inputFieldsByName = mapObjectValueFieldsByName(inputValue); - - - List inputFieldTypes = fieldVisibility.getFieldDefinitions(type); - for (GraphQLInputObjectField inputFieldDefinition : inputFieldTypes) { - GraphQLInputType fieldType = inputFieldDefinition.getType(); - String fieldName = inputFieldDefinition.getName(); - ObjectField field = inputFieldsByName.get(fieldName); - boolean hasValue = field != null; - Object value; - Value fieldValue = field != null ? field.getValue() : null; - if (fieldValue instanceof VariableReference) { - String variableName = ((VariableReference) fieldValue).getName(); - hasValue = coercedVariables.containsKey(variableName); - value = coercedVariables.get(variableName); - } else { - value = fieldValue; - } - if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { - Object coercedDefaultValue = defaultValueToInternalValue(fieldVisibility, - inputFieldDefinition.getInputFieldDefaultValue(), - fieldType); - coercedValues.put(fieldName, coercedDefaultValue); - } else if (isNonNull(fieldType) && (!hasValue || isNullValue(value))) { - return assertShouldNeverHappen("Should have been validated before"); - } else if (hasValue) { - if (isNullValue(value)) { - coercedValues.put(fieldName, value); - } else if (fieldValue instanceof VariableReference) { - coercedValues.put(fieldName, value); - } else { - value = literalToInternalValue(fieldVisibility, fieldType, fieldValue, coercedVariables); - coercedValues.put(fieldName, value); - } - } - } - return coercedValues; - } - - private static boolean isNullValue(Object value) { - if (value == null) { - return true; - } - if (!(value instanceof NormalizedInputValue)) { - return false; - } - return ((NormalizedInputValue) value).getValue() == null; - } - - private static Map mapObjectValueFieldsByName(ObjectValue inputValue) { - Map inputValueFieldsByName = new LinkedHashMap<>(); - for (ObjectField objectField : inputValue.getObjectFields()) { - inputValueFieldsByName.put(objectField.getName(), objectField); - } - return inputValueFieldsByName; - } - - private static Object defaultValueToInternalValue(GraphqlFieldVisibility fieldVisibility, - InputValueWithState defaultValue, - GraphQLInputType type - ) { - if (defaultValue.isInternal()) { - return defaultValue.getValue(); - } - if (defaultValue.isLiteral()) { - // default value literals can't reference variables, this is why the variables are empty - return literalToInternalValue(fieldVisibility, type, (Value) defaultValue.getValue(), CoercedVariables.emptyVariables()); - } - if (defaultValue.isExternal()) { - // performs validation too - return externalValueToInternalValue(fieldVisibility, type, defaultValue.getValue()); - } - return assertShouldNeverHappen(); - } - - - /* - * ======================LEGACY=======+TO BE REMOVED IN THE FUTURE =============== - */ - - /** - * Legacy logic to convert an arbitrary java object to an Ast Literal. - * Only provided here to preserve backwards compatibility. - */ - @VisibleForTesting - static Value valueToLiteralLegacy(Object value, GraphQLType type) { - assertTrue(!(value instanceof Value), () -> "Unexpected literal " + value); - if (value == null) { - return null; - } - - if (isNonNull(type)) { - return handleNonNullLegacy(value, (GraphQLNonNull) type); - } - - // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but - // the value is not an array, convert the value using the list's item type. - if (isList(type)) { - return handleListLegacy(value, (GraphQLList) type); - } - - // Populate the fields of the input object by creating ASTs from each value - // in the JavaScript object according to the fields in the input type. - if (type instanceof GraphQLInputObjectType) { - return handleInputObjectLegacy(value, (GraphQLInputObjectType) type); - } - - if (!(type instanceof GraphQLScalarType || type instanceof GraphQLEnumType)) { - throw new AssertException("Must provide Input Type, cannot use: " + type.getClass()); - } - - // Since value is an internally represented value, it must be serialized - // to an externally represented value before converting into an AST. - final Object serialized = serializeLegacy(type, value); - if (isNullishLegacy(serialized)) { - return null; - } - - // Others serialize based on their corresponding JavaScript scalar types. - if (serialized instanceof Boolean) { - return BooleanValue.newBooleanValue().value((Boolean) serialized).build(); - } - - String stringValue = serialized.toString(); - // numbers can be Int or Float values. - if (serialized instanceof Number) { - return handleNumberLegacy(stringValue); - } - - if (serialized instanceof String) { - // Enum types use Enum literals. - if (type instanceof GraphQLEnumType) { - return EnumValue.newEnumValue().name(stringValue).build(); - } - - // ID types can use Int literals. - if (type == Scalars.GraphQLID && stringValue.matches("^[0-9]+$")) { - return IntValue.newIntValue().value(new BigInteger(stringValue)).build(); - } - - return StringValue.newStringValue().value(stringValue).build(); - } - - throw new AssertException("'Cannot convert value to AST: " + serialized); - } - - private static Value handleInputObjectLegacy(Object javaValue, GraphQLInputObjectType type) { - List fields = type.getFields(); - List fieldNodes = new ArrayList<>(); - fields.forEach(field -> { - String fieldName = field.getName(); - GraphQLInputType fieldType = field.getType(); - Object fieldValueObj = PropertyDataFetcherHelper.getPropertyValue(fieldName, javaValue, fieldType); - Value nodeValue = valueToLiteralLegacy(fieldValueObj, fieldType); - if (nodeValue != null) { - fieldNodes.add(newObjectField().name(fieldName).value(nodeValue).build()); - } - }); - return ObjectValue.newObjectValue().objectFields(fieldNodes).build(); - } - - private static Value handleNumberLegacy(String stringValue) { - if (stringValue.matches("^[0-9]+$")) { - return IntValue.newIntValue().value(new BigInteger(stringValue)).build(); - } else { - return FloatValue.newFloatValue().value(new BigDecimal(stringValue)).build(); - } - } - - @SuppressWarnings("rawtypes") - private static Value handleListLegacy(Object value, GraphQLList type) { - GraphQLType itemType = type.getWrappedType(); - if (FpKit.isIterable(value)) { - List valuesNodes = FpKit.toListOrSingletonList(value) - .stream() - .map(item -> valueToLiteralLegacy(item, itemType)) - .collect(toList()); - return ArrayValue.newArrayValue().values(valuesNodes).build(); - } - return valueToLiteralLegacy(value, itemType); - } - - private static Value handleNonNullLegacy(Object _value, GraphQLNonNull type) { - GraphQLType wrappedType = type.getWrappedType(); - return valueToLiteralLegacy(_value, wrappedType); - } - - private static Object serializeLegacy(GraphQLType type, Object value) { - if (type instanceof GraphQLScalarType) { - return ((GraphQLScalarType) type).getCoercing().serialize(value); - } else { - return ((GraphQLEnumType) type).serialize(value); - } - } - - private static boolean isNullishLegacy(Object serialized) { - if (serialized instanceof Number) { - return Double.isNaN(((Number) serialized).doubleValue()); - } - return serialized == null; - } - /** * @return true if variable is absent from input, and if value is NOT a variable then false */ diff --git a/src/main/java/graphql/execution/ValuesResolverConversion.java b/src/main/java/graphql/execution/ValuesResolverConversion.java new file mode 100644 index 0000000000..9c32048dc9 --- /dev/null +++ b/src/main/java/graphql/execution/ValuesResolverConversion.java @@ -0,0 +1,568 @@ +package graphql.execution; + +import com.google.common.collect.ImmutableList; +import graphql.GraphQLContext; +import graphql.Internal; +import graphql.language.ArrayValue; +import graphql.language.NullValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.language.Value; +import graphql.language.VariableDefinition; +import graphql.language.VariableReference; +import graphql.normalized.NormalizedInputValue; +import graphql.schema.CoercingParseValueException; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeUtil; +import graphql.schema.InputValueWithState; +import graphql.schema.visibility.DefaultGraphqlFieldVisibility; +import graphql.schema.visibility.GraphqlFieldVisibility; +import graphql.util.FpKit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static graphql.Assert.assertShouldNeverHappen; +import static graphql.Assert.assertTrue; +import static graphql.collect.ImmutableKit.emptyList; +import static graphql.collect.ImmutableKit.map; +import static graphql.execution.ValuesResolver.ValueMode.NORMALIZED; +import static graphql.language.NullValue.newNullValue; +import static graphql.language.ObjectField.newObjectField; +import static graphql.schema.GraphQLTypeUtil.isList; +import static graphql.schema.GraphQLTypeUtil.isNonNull; +import static graphql.schema.GraphQLTypeUtil.simplePrint; +import static graphql.schema.GraphQLTypeUtil.unwrapNonNull; +import static graphql.schema.GraphQLTypeUtil.unwrapOne; +import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY; +import static java.util.stream.Collectors.toList; + +/** + * This class, originally broken out from {@link ValuesResolver} contains code for the conversion of values + * from one form (literal, external etc..) to another. + */ +@SuppressWarnings("rawtypes") +@Internal +class ValuesResolverConversion { + + static Object valueToLiteralImpl(GraphqlFieldVisibility fieldVisibility, + InputValueWithState inputValueWithState, + GraphQLType type, + ValuesResolver.ValueMode valueMode, + GraphQLContext graphqlContext, + Locale locale) { + if (inputValueWithState.isInternal()) { + if (valueMode == NORMALIZED) { + return assertShouldNeverHappen("can't infer normalized structure"); + } + return ValuesResolverLegacy.valueToLiteralLegacy(inputValueWithState.getValue(), type, graphqlContext, locale); + } + if (inputValueWithState.isLiteral()) { + return inputValueWithState.getValue(); + } + if (inputValueWithState.isExternal()) { + return externalValueToLiteral(fieldVisibility, inputValueWithState.getValue(), (GraphQLInputType) type, valueMode, graphqlContext, locale); + } + return assertShouldNeverHappen("unexpected value state " + inputValueWithState); + } + + /** + * Converts an external value to an internal value + * + * @param fieldVisibility the field visibility to use + * @param externalValue the input external value + * @param type the type of input value + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use + * + * @return a value converted to an internal value + */ + static Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility, + Object externalValue, + GraphQLInputType type, + GraphQLContext graphqlContext, + Locale locale) { + return externalValueToInternalValueImpl(fieldVisibility, type, externalValue, graphqlContext, locale); + } + + @Nullable + static Object valueToInternalValueImpl(InputValueWithState inputValueWithState, GraphQLType type, GraphQLContext graphqlContext, Locale locale) { + DefaultGraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY; + if (inputValueWithState.isInternal()) { + return inputValueWithState.getValue(); + } + if (inputValueWithState.isLiteral()) { + return literalToInternalValue(fieldVisibility, type, (Value) inputValueWithState.getValue(), CoercedVariables.emptyVariables(), graphqlContext, locale); + } + if (inputValueWithState.isExternal()) { + return externalValueToInternalValueImpl(fieldVisibility, type, inputValueWithState.getValue(), graphqlContext, locale); + } + return assertShouldNeverHappen("unexpected value state " + inputValueWithState); + } + + /** + * No validation: the external value is assumed to be valid. + */ + static Object externalValueToLiteral(GraphqlFieldVisibility fieldVisibility, + @Nullable Object value, + GraphQLInputType type, + ValuesResolver.ValueMode valueMode, + GraphQLContext graphqlContext, + Locale locale) { + if (value == null) { + return newNullValue().build(); + } + if (GraphQLTypeUtil.isNonNull(type)) { + return externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) unwrapNonNull(type), valueMode, graphqlContext, locale); + } + if (type instanceof GraphQLScalarType) { + return externalValueToLiteralForScalar((GraphQLScalarType) type, value, graphqlContext, locale); + } else if (type instanceof GraphQLEnumType) { + return externalValueToLiteralForEnum((GraphQLEnumType) type, value, graphqlContext, locale); + } else if (type instanceof GraphQLList) { + return externalValueToLiteralForList(fieldVisibility, (GraphQLList) type, value, valueMode, graphqlContext, locale); + } else if (type instanceof GraphQLInputObjectType) { + return externalValueToLiteralForObject(fieldVisibility, (GraphQLInputObjectType) type, value, valueMode, graphqlContext, locale); + } else { + return assertShouldNeverHappen("unexpected type %s", type); + } + } + + /** + * No validation + */ + private static Value externalValueToLiteralForScalar(GraphQLScalarType scalarType, Object value, GraphQLContext graphqlContext, @NotNull Locale locale) { + return scalarType.getCoercing().valueToLiteral(value, graphqlContext, locale); + + } + + /** + * No validation + */ + private static Value externalValueToLiteralForEnum(GraphQLEnumType enumType, Object value, GraphQLContext graphqlContext, Locale locale) { + return enumType.valueToLiteral(value, graphqlContext, locale); + } + + /** + * No validation + */ + @SuppressWarnings("unchecked") + private static Object externalValueToLiteralForList(GraphqlFieldVisibility fieldVisibility, + GraphQLList listType, + Object value, + ValuesResolver.ValueMode valueMode, + GraphQLContext graphqlContext, + Locale locale) { + GraphQLInputType wrappedType = (GraphQLInputType) listType.getWrappedType(); + List result = FpKit.toListOrSingletonList(value) + .stream() + .map(val -> externalValueToLiteral(fieldVisibility, val, wrappedType, valueMode, graphqlContext, locale)) + .collect(toList()); + if (valueMode == NORMALIZED) { + return result; + } else { + return ArrayValue.newArrayValue().values((List) result).build(); + } + } + + /** + * No validation + */ + @SuppressWarnings("unchecked") + private static Object externalValueToLiteralForObject(GraphqlFieldVisibility fieldVisibility, + GraphQLInputObjectType inputObjectType, + Object inputValue, + ValuesResolver.ValueMode valueMode, + GraphQLContext graphqlContext, Locale locale) { + assertTrue(inputValue instanceof Map, () -> "Expect Map as input"); + Map inputMap = (Map) inputValue; + List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType); + + Map normalizedResult = new LinkedHashMap<>(); + ImmutableList.Builder objectFields = ImmutableList.builder(); + for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) { + GraphQLInputType fieldType = inputFieldDefinition.getType(); + String fieldName = inputFieldDefinition.getName(); + boolean hasValue = inputMap.containsKey(fieldName); + Object fieldValue = inputMap.getOrDefault(fieldName, null); + if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { + //TODO: consider valueMode + Object defaultValueLiteral = valueToLiteralImpl(fieldVisibility, inputFieldDefinition.getInputFieldDefaultValue(), fieldType, ValuesResolver.ValueMode.LITERAL, graphqlContext, locale); + if (valueMode == ValuesResolver.ValueMode.LITERAL) { + normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), defaultValueLiteral)); + } else { + objectFields.add(newObjectField().name(fieldName).value((Value) defaultValueLiteral).build()); + } + } else if (hasValue) { + if (fieldValue == null) { + if (valueMode == NORMALIZED) { + normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), null)); + } else { + objectFields.add(newObjectField().name(fieldName).value(newNullValue().build()).build()); + } + } else { + Object literal = externalValueToLiteral(fieldVisibility, + fieldValue, + fieldType, + valueMode, + graphqlContext, locale); + if (valueMode == NORMALIZED) { + normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), literal)); + } else { + objectFields.add(newObjectField().name(fieldName).value((Value) literal).build()); + } + } + } + } + if (valueMode == NORMALIZED) { + return normalizedResult; + } + return ObjectValue.newObjectValue().objectFields(objectFields.build()).build(); + } + + /** + * performs validation too + */ + static CoercedVariables externalValueToInternalValueForVariables(GraphQLSchema schema, + List variableDefinitions, + RawVariables rawVariables, + GraphQLContext graphqlContext, Locale locale) { + GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility(); + Map coercedValues = new LinkedHashMap<>(); + for (VariableDefinition variableDefinition : variableDefinitions) { + try { + String variableName = variableDefinition.getName(); + GraphQLType variableType = TypeFromAST.getTypeFromAST(schema, variableDefinition.getType()); + assertTrue(variableType instanceof GraphQLInputType); + // can be NullValue + Value defaultValue = variableDefinition.getDefaultValue(); + boolean hasValue = rawVariables.containsKey(variableName); + Object value = rawVariables.get(variableName); + if (!hasValue && defaultValue != null) { + Object coercedDefaultValue = literalToInternalValue(fieldVisibility, variableType, defaultValue, CoercedVariables.emptyVariables(), graphqlContext, locale); + coercedValues.put(variableName, coercedDefaultValue); + } else if (isNonNull(variableType) && (!hasValue || value == null)) { + throw new NonNullableValueCoercedAsNullException(variableDefinition, variableType); + } else if (hasValue) { + if (value == null) { + coercedValues.put(variableName, null); + } else { + Object coercedValue = externalValueToInternalValueImpl(fieldVisibility, variableType, value, graphqlContext, locale); + coercedValues.put(variableName, coercedValue); + } + } + } catch (CoercingParseValueException e) { + throw CoercingParseValueException.newCoercingParseValueException() + .message(String.format("Variable '%s' has an invalid value: %s", variableDefinition.getName(), e.getMessage())) + .extensions(e.getExtensions()) + .cause(e.getCause()) + .sourceLocation(variableDefinition.getSourceLocation()) + .build(); + } catch (NonNullableValueCoercedAsNullException e) { + throw new NonNullableValueCoercedAsNullException(variableDefinition, e.getMessage()); + } + } + + return CoercedVariables.of(coercedValues); + } + + /** + * Performs validation too + */ + @SuppressWarnings("unchecked") + static Object externalValueToInternalValueImpl(GraphqlFieldVisibility fieldVisibility, + GraphQLType graphQLType, + Object value, + GraphQLContext graphqlContext, + Locale locale) throws NonNullableValueCoercedAsNullException, CoercingParseValueException { + if (isNonNull(graphQLType)) { + Object returnValue = + externalValueToInternalValueImpl(fieldVisibility, unwrapOne(graphQLType), value, graphqlContext, locale); + if (returnValue == null) { + throw new NonNullableValueCoercedAsNullException(graphQLType); + } + return returnValue; + } + + if (value == null) { + return null; + } + + if (graphQLType instanceof GraphQLScalarType) { + return externalValueToInternalValueForScalar((GraphQLScalarType) graphQLType, value, graphqlContext, locale); + } else if (graphQLType instanceof GraphQLEnumType) { + return externalValueToInternalValueForEnum((GraphQLEnumType) graphQLType, value, graphqlContext, locale); + } else if (graphQLType instanceof GraphQLList) { + return externalValueToInternalValueForList(fieldVisibility, (GraphQLList) graphQLType, value, graphqlContext, locale); + } else if (graphQLType instanceof GraphQLInputObjectType) { + if (value instanceof Map) { + return externalValueToInternalValueForObject(fieldVisibility, (GraphQLInputObjectType) graphQLType, (Map) value, graphqlContext, locale); + } else { + throw CoercingParseValueException.newCoercingParseValueException() + .message("Expected type 'Map' but was '" + value.getClass().getSimpleName() + + "'. Variables for input objects must be an instance of type 'Map'.") + .build(); + } + } else { + return assertShouldNeverHappen("unhandled type %s", graphQLType); + } + } + + /** + * performs validation + */ + private static Object externalValueToInternalValueForObject(GraphqlFieldVisibility fieldVisibility, + GraphQLInputObjectType inputObjectType, + Map inputMap, + GraphQLContext graphqlContext, + Locale locale) throws NonNullableValueCoercedAsNullException, CoercingParseValueException { + List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType); + List fieldNames = map(fieldDefinitions, GraphQLInputObjectField::getName); + for (String providedFieldName : inputMap.keySet()) { + if (!fieldNames.contains(providedFieldName)) { + throw new InputMapDefinesTooManyFieldsException(inputObjectType, providedFieldName); + } + } + + Map coercedValues = new LinkedHashMap<>(); + + for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) { + GraphQLInputType fieldType = inputFieldDefinition.getType(); + String fieldName = inputFieldDefinition.getName(); + InputValueWithState defaultValue = inputFieldDefinition.getInputFieldDefaultValue(); + boolean hasValue = inputMap.containsKey(fieldName); + Object value = inputMap.getOrDefault(fieldName, null); + if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { + Object coercedDefaultValue = defaultValueToInternalValue(fieldVisibility, + defaultValue, + fieldType, graphqlContext, locale); + coercedValues.put(fieldName, coercedDefaultValue); + } else if (isNonNull(fieldType) && (!hasValue || value == null)) { + throw new NonNullableValueCoercedAsNullException(fieldName, emptyList(), fieldType); + } else if (hasValue) { + if (value == null) { + coercedValues.put(fieldName, null); + } else { + value = externalValueToInternalValueImpl(fieldVisibility, + fieldType, value, graphqlContext, locale); + coercedValues.put(fieldName, value); + } + } + } + return coercedValues; + } + + /** + * including validation + */ + private static Object externalValueToInternalValueForScalar(GraphQLScalarType graphQLScalarType, Object value, GraphQLContext graphqlContext, Locale locale) throws CoercingParseValueException { + return graphQLScalarType.getCoercing().parseValue(value, graphqlContext, locale); + } + + /** + * including validation + */ + private static Object externalValueToInternalValueForEnum(GraphQLEnumType graphQLEnumType, Object value, GraphQLContext graphqlContext, Locale locale) throws CoercingParseValueException { + return graphQLEnumType.parseValue(value, graphqlContext, locale); + } + + /** + * including validation + */ + private static List externalValueToInternalValueForList(GraphqlFieldVisibility fieldVisibility, + GraphQLList graphQLList, + Object value, + GraphQLContext graphqlContext, + Locale locale) throws CoercingParseValueException, NonNullableValueCoercedAsNullException { + + GraphQLType wrappedType = graphQLList.getWrappedType(); + return FpKit.toListOrSingletonList(value) + .stream() + .map(val -> externalValueToInternalValueImpl(fieldVisibility, wrappedType, val, graphqlContext, locale)) + .collect(toList()); + } + + /** + * No validation (it was checked before via ArgumentsOfCorrectType and VariableDefaultValuesOfCorrectType) + * + * @param fieldVisibility the field visibility + * @param type the type of the input value + * @param inputValue the AST literal to be changed + * @param coercedVariables the coerced variable values + * @param graphqlContext the GraphqlContext to use + * @param locale the Locale to use + * + * @return literal converted to an internal value + */ + static Object literalToInternalValue(GraphqlFieldVisibility fieldVisibility, + GraphQLType type, + Value inputValue, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { + + return literalToInternalValueImpl(fieldVisibility, type, inputValue, coercedVariables, graphqlContext, locale); + } + + @Nullable + private static Object literalToInternalValueImpl(GraphqlFieldVisibility fieldVisibility, GraphQLType type, Value inputValue, CoercedVariables coercedVariables, GraphQLContext graphqlContext, Locale locale) { + if (inputValue instanceof VariableReference) { + return coercedVariables.get(((VariableReference) inputValue).getName()); + } + if (inputValue instanceof NullValue) { + return null; + } + if (type instanceof GraphQLScalarType) { + return literalToInternalValueForScalar(inputValue, (GraphQLScalarType) type, coercedVariables, graphqlContext, locale); + } + if (isNonNull(type)) { + return literalToInternalValue(fieldVisibility, unwrapOne(type), inputValue, coercedVariables, graphqlContext, locale); + } + if (type instanceof GraphQLInputObjectType) { + return literalToInternalValueForInputObject(fieldVisibility, (GraphQLInputObjectType) type, (ObjectValue) inputValue, coercedVariables, graphqlContext, locale); + } + if (type instanceof GraphQLEnumType) { + return ((GraphQLEnumType) type).parseLiteral(inputValue, graphqlContext, locale); + } + if (isList(type)) { + return literalToInternalValueForList(fieldVisibility, (GraphQLList) type, inputValue, coercedVariables, graphqlContext, locale); + } + return null; + } + + /** + * no validation + */ + private static Object literalToInternalValueForScalar(Value inputValue, GraphQLScalarType scalarType, CoercedVariables coercedVariables, GraphQLContext graphqlContext, @NotNull Locale locale) { + // the CoercingParseLiteralException exception that could happen here has been validated earlier via ValidationUtil + return scalarType.getCoercing().parseLiteral(inputValue, coercedVariables, graphqlContext, locale); + } + + /** + * no validation + */ + private static Object literalToInternalValueForList(GraphqlFieldVisibility fieldVisibility, + GraphQLList graphQLList, + Value value, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { + + if (value instanceof ArrayValue) { + ArrayValue arrayValue = (ArrayValue) value; + List result = new ArrayList<>(); + for (Value singleValue : arrayValue.getValues()) { + result.add(literalToInternalValue(fieldVisibility, graphQLList.getWrappedType(), singleValue, coercedVariables, graphqlContext, locale)); + } + return result; + } else { + return Collections.singletonList( + literalToInternalValue(fieldVisibility, + graphQLList.getWrappedType(), + value, + coercedVariables, + graphqlContext, locale)); + } + } + + /** + * no validation + */ + private static Object literalToInternalValueForInputObject(GraphqlFieldVisibility fieldVisibility, + GraphQLInputObjectType type, + ObjectValue inputValue, + CoercedVariables coercedVariables, + GraphQLContext graphqlContext, + Locale locale) { + Map coercedValues = new LinkedHashMap<>(); + + Map inputFieldsByName = mapObjectValueFieldsByName(inputValue); + + + List inputFieldTypes = fieldVisibility.getFieldDefinitions(type); + for (GraphQLInputObjectField inputFieldDefinition : inputFieldTypes) { + GraphQLInputType fieldType = inputFieldDefinition.getType(); + String fieldName = inputFieldDefinition.getName(); + ObjectField field = inputFieldsByName.get(fieldName); + boolean hasValue = field != null; + Object value; + Value fieldValue = field != null ? field.getValue() : null; + if (fieldValue instanceof VariableReference) { + String variableName = ((VariableReference) fieldValue).getName(); + hasValue = coercedVariables.containsKey(variableName); + value = coercedVariables.get(variableName); + } else { + value = fieldValue; + } + if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) { + Object coercedDefaultValue = defaultValueToInternalValue(fieldVisibility, + inputFieldDefinition.getInputFieldDefaultValue(), + fieldType, + graphqlContext, locale); + coercedValues.put(fieldName, coercedDefaultValue); + } else if (isNonNull(fieldType) && (!hasValue || isNullValue(value))) { + return assertShouldNeverHappen("Should have been validated before"); + } else if (hasValue) { + if (isNullValue(value)) { + coercedValues.put(fieldName, value); + } else if (fieldValue instanceof VariableReference) { + coercedValues.put(fieldName, value); + } else { + value = literalToInternalValue(fieldVisibility, fieldType, fieldValue, coercedVariables, graphqlContext, locale); + coercedValues.put(fieldName, value); + } + } + } + return coercedValues; + } + + static boolean isNullValue(Object value) { + if (value == null) { + return true; + } + if (!(value instanceof NormalizedInputValue)) { + return false; + } + return ((NormalizedInputValue) value).getValue() == null; + } + + private static Map mapObjectValueFieldsByName(ObjectValue inputValue) { + Map inputValueFieldsByName = new LinkedHashMap<>(); + for (ObjectField objectField : inputValue.getObjectFields()) { + inputValueFieldsByName.put(objectField.getName(), objectField); + } + return inputValueFieldsByName; + } + + static Object defaultValueToInternalValue(GraphqlFieldVisibility fieldVisibility, + InputValueWithState defaultValue, + GraphQLInputType type, + GraphQLContext graphqlContext, + Locale locale) { + if (defaultValue.isInternal()) { + return defaultValue.getValue(); + } + if (defaultValue.isLiteral()) { + // default value literals can't reference variables, this is why the variables are empty + return literalToInternalValue(fieldVisibility, type, (Value) defaultValue.getValue(), CoercedVariables.emptyVariables(), graphqlContext, locale); + } + if (defaultValue.isExternal()) { + // performs validation too + return externalValueToInternalValueImpl(fieldVisibility, type, defaultValue.getValue(), graphqlContext, locale); + } + return assertShouldNeverHappen(); + } +} diff --git a/src/main/java/graphql/execution/ValuesResolverLegacy.java b/src/main/java/graphql/execution/ValuesResolverLegacy.java new file mode 100644 index 0000000000..5923f5248e --- /dev/null +++ b/src/main/java/graphql/execution/ValuesResolverLegacy.java @@ -0,0 +1,167 @@ +package graphql.execution; + +import graphql.AssertException; +import graphql.GraphQLContext; +import graphql.Internal; +import graphql.Scalars; +import graphql.VisibleForTesting; +import graphql.language.ArrayValue; +import graphql.language.BooleanValue; +import graphql.language.EnumValue; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.ObjectField; +import graphql.language.ObjectValue; +import graphql.language.StringValue; +import graphql.language.Value; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLType; +import graphql.schema.PropertyDataFetcherHelper; +import graphql.util.FpKit; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static graphql.Assert.assertTrue; +import static graphql.language.ObjectField.newObjectField; +import static graphql.schema.GraphQLTypeUtil.isList; +import static graphql.schema.GraphQLTypeUtil.isNonNull; +import static java.util.stream.Collectors.toList; + +/* + * ======================LEGACY=======+TO BE REMOVED IN THE FUTURE =============== + */ + +@Internal +class ValuesResolverLegacy { + /** + * Legacy logic to convert an arbitrary java object to an Ast Literal. + * Only provided here to preserve backwards compatibility. + */ + @VisibleForTesting + static Value valueToLiteralLegacy(Object value, GraphQLType type, GraphQLContext graphqlContext, Locale locale) { + assertTrue(!(value instanceof Value), () -> "Unexpected literal " + value); + if (value == null) { + return null; + } + + if (isNonNull(type)) { + return handleNonNullLegacy(value, (GraphQLNonNull) type, graphqlContext, locale); + } + + // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but + // the value is not an array, convert the value using the list's item type. + if (isList(type)) { + return handleListLegacy(value, (GraphQLList) type, graphqlContext, locale); + } + + // Populate the fields of the input object by creating ASTs from each value + // in the JavaScript object according to the fields in the input type. + if (type instanceof GraphQLInputObjectType) { + return handleInputObjectLegacy(value, (GraphQLInputObjectType) type, graphqlContext, locale); + } + + if (!(type instanceof GraphQLScalarType || type instanceof GraphQLEnumType)) { + throw new AssertException("Must provide Input Type, cannot use: " + type.getClass()); + } + + // Since value is an internally represented value, it must be serialized + // to an externally represented value before converting into an AST. + final Object serialized = serializeLegacy(type, value, graphqlContext, locale); + if (isNullishLegacy(serialized)) { + return null; + } + + // Others serialize based on their corresponding JavaScript scalar types. + if (serialized instanceof Boolean) { + return BooleanValue.newBooleanValue().value((Boolean) serialized).build(); + } + + String stringValue = serialized.toString(); + // numbers can be Int or Float values. + if (serialized instanceof Number) { + return handleNumberLegacy(stringValue); + } + + if (serialized instanceof String) { + // Enum types use Enum literals. + if (type instanceof GraphQLEnumType) { + return EnumValue.newEnumValue().name(stringValue).build(); + } + + // ID types can use Int literals. + if (type == Scalars.GraphQLID && stringValue.matches("^[0-9]+$")) { + return IntValue.newIntValue().value(new BigInteger(stringValue)).build(); + } + + return StringValue.newStringValue().value(stringValue).build(); + } + + throw new AssertException("'Cannot convert value to AST: " + serialized); + } + + private static Value handleInputObjectLegacy(Object javaValue, GraphQLInputObjectType type, GraphQLContext graphqlContext, Locale locale) { + List fields = type.getFields(); + List fieldNodes = new ArrayList<>(); + fields.forEach(field -> { + String fieldName = field.getName(); + GraphQLInputType fieldType = field.getType(); + Object fieldValueObj = PropertyDataFetcherHelper.getPropertyValue(fieldName, javaValue, fieldType); + Value nodeValue = valueToLiteralLegacy(fieldValueObj, fieldType, graphqlContext, locale); + if (nodeValue != null) { + fieldNodes.add(newObjectField().name(fieldName).value(nodeValue).build()); + } + }); + return ObjectValue.newObjectValue().objectFields(fieldNodes).build(); + } + + private static Value handleNumberLegacy(String stringValue) { + if (stringValue.matches("^[0-9]+$")) { + return IntValue.newIntValue().value(new BigInteger(stringValue)).build(); + } else { + return FloatValue.newFloatValue().value(new BigDecimal(stringValue)).build(); + } + } + + @SuppressWarnings("rawtypes") + private static Value handleListLegacy(Object value, GraphQLList type, GraphQLContext graphqlContext, Locale locale) { + GraphQLType itemType = type.getWrappedType(); + if (FpKit.isIterable(value)) { + List valuesNodes = FpKit.toListOrSingletonList(value) + .stream() + .map(item -> valueToLiteralLegacy(item, itemType, graphqlContext, locale)) + .collect(toList()); + return ArrayValue.newArrayValue().values(valuesNodes).build(); + } + return valueToLiteralLegacy(value, itemType, graphqlContext, locale); + } + + private static Value handleNonNullLegacy(Object _value, GraphQLNonNull type, GraphQLContext graphqlContext, Locale locale) { + GraphQLType wrappedType = type.getWrappedType(); + return valueToLiteralLegacy(_value, wrappedType, graphqlContext, locale); + } + + private static Object serializeLegacy(GraphQLType type, Object value, GraphQLContext graphqlContext, Locale locale) { + if (type instanceof GraphQLScalarType) { + return ((GraphQLScalarType) type).getCoercing().serialize(value, graphqlContext, locale); + } else { + return ((GraphQLEnumType) type).serialize(value, graphqlContext, locale); + } + } + + private static boolean isNullishLegacy(Object serialized) { + if (serialized instanceof Number) { + return Double.isNaN(((Number) serialized).doubleValue()); + } + return serialized == null; + } +} diff --git a/src/main/java/graphql/execution/directives/DirectivesResolver.java b/src/main/java/graphql/execution/directives/DirectivesResolver.java index 9c3d39a8c3..84722c3e40 100644 --- a/src/main/java/graphql/execution/directives/DirectivesResolver.java +++ b/src/main/java/graphql/execution/directives/DirectivesResolver.java @@ -1,6 +1,7 @@ package graphql.execution.directives; import com.google.common.collect.ImmutableMap; +import graphql.GraphQLContext; import graphql.Internal; import graphql.execution.CoercedVariables; import graphql.execution.ValuesResolver; @@ -12,6 +13,7 @@ import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -23,21 +25,27 @@ public class DirectivesResolver { public DirectivesResolver() { } - public Map resolveDirectives(List directives, GraphQLSchema schema, Map variables) { + public Map resolveDirectives(List directives, GraphQLSchema schema, Map variables, GraphQLContext graphQLContext, Locale locale) { GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry(); Map directiveMap = new LinkedHashMap<>(); directives.forEach(directive -> { GraphQLDirective protoType = schema.getDirective(directive.getName()); if (protoType != null) { - GraphQLDirective newDirective = protoType.transform(builder -> buildArguments(builder, codeRegistry, protoType, directive, variables)); + GraphQLDirective newDirective = protoType.transform(builder -> buildArguments(builder, codeRegistry, protoType, directive, variables, graphQLContext, locale)); directiveMap.put(newDirective.getName(), newDirective); } }); return ImmutableMap.copyOf(directiveMap); } - private void buildArguments(GraphQLDirective.Builder directiveBuilder, GraphQLCodeRegistry codeRegistry, GraphQLDirective protoType, Directive fieldDirective, Map variables) { - Map argumentValues = ValuesResolver.getArgumentValues(codeRegistry, protoType.getArguments(), fieldDirective.getArguments(), CoercedVariables.of(variables)); + private void buildArguments(GraphQLDirective.Builder directiveBuilder, + GraphQLCodeRegistry codeRegistry, + GraphQLDirective protoType, + Directive fieldDirective, + Map variables, + GraphQLContext graphQLContext, + Locale locale) { + Map argumentValues = ValuesResolver.getArgumentValues(codeRegistry, protoType.getArguments(), fieldDirective.getArguments(), CoercedVariables.of(variables), graphQLContext, locale); directiveBuilder.clearArguments(); protoType.getArguments().forEach(protoArg -> { if (argumentValues.containsKey(protoArg.getName())) { diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java index 40cb79c289..5deb4205b1 100644 --- a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java +++ b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java @@ -2,6 +2,7 @@ import graphql.Assert; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.language.Argument; import graphql.language.Value; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Locale; import java.util.function.Consumer; import static graphql.Assert.assertNotNull; @@ -83,7 +85,7 @@ public boolean hasSetValue() { */ @Nullable public T getValue() { - return getInputValueImpl(getType(), value); + return getInputValueImpl(getType(), value, GraphQLContext.getDefault(), Locale.getDefault()); } /** diff --git a/src/main/java/graphql/execution/directives/QueryDirectivesImpl.java b/src/main/java/graphql/execution/directives/QueryDirectivesImpl.java index b409abb1b0..85468c3470 100644 --- a/src/main/java/graphql/execution/directives/QueryDirectivesImpl.java +++ b/src/main/java/graphql/execution/directives/QueryDirectivesImpl.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import graphql.GraphQLContext; import graphql.Internal; import graphql.collect.ImmutableKit; import graphql.execution.MergedField; @@ -14,6 +15,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static graphql.collect.ImmutableKit.emptyList; @@ -30,15 +32,19 @@ public class QueryDirectivesImpl implements QueryDirectives { private final MergedField mergedField; private final GraphQLSchema schema; private final Map variables; + private final GraphQLContext graphQLContext; + private final Locale locale; private volatile ImmutableMap> fieldDirectivesByField; private volatile ImmutableMap> fieldDirectivesByName; private volatile ImmutableMap> fieldAppliedDirectivesByField; private volatile ImmutableMap> fieldAppliedDirectivesByName; - public QueryDirectivesImpl(MergedField mergedField, GraphQLSchema schema, Map variables) { + public QueryDirectivesImpl(MergedField mergedField, GraphQLSchema schema, Map variables, GraphQLContext graphQLContext, Locale locale) { this.mergedField = mergedField; this.schema = schema; this.variables = variables; + this.graphQLContext = graphQLContext; + this.locale = locale; } private void computeValuesLazily() { @@ -53,7 +59,7 @@ private void computeValuesLazily() { List directives = field.getDirectives(); ImmutableList resolvedDirectives = ImmutableList.copyOf( directivesResolver - .resolveDirectives(directives, schema, variables) + .resolveDirectives(directives, schema, variables, graphQLContext, locale) .values() ); byField.put(field, resolvedDirectives); diff --git a/src/main/java/graphql/i18n/I18n.java b/src/main/java/graphql/i18n/I18n.java index dd9efae54e..bf9b5aafc3 100644 --- a/src/main/java/graphql/i18n/I18n.java +++ b/src/main/java/graphql/i18n/I18n.java @@ -1,9 +1,11 @@ package graphql.i18n; +import graphql.GraphQLContext; import graphql.Internal; import graphql.VisibleForTesting; import java.text.MessageFormat; +import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -14,10 +16,12 @@ @Internal public class I18n { + /** * This enum is a type safe way to control what resource bundle to load from */ public enum BundleType { + Scalars, Validation, Execution, General; @@ -30,14 +34,20 @@ public enum BundleType { } private final ResourceBundle resourceBundle; + private final Locale locale; @VisibleForTesting protected I18n(BundleType bundleType, Locale locale) { + this.locale = locale; assertNotNull(bundleType); assertNotNull(locale); this.resourceBundle = ResourceBundle.getBundle(bundleType.baseName, locale); } + public Locale getLocale() { + return locale; + } + public ResourceBundle getResourceBundle() { return resourceBundle; } @@ -55,16 +65,29 @@ public static I18n i18n(BundleType bundleType, Locale locale) { * * @return the formatted I18N message */ - @SuppressWarnings("UnnecessaryLocalVariable") public String msg(String msgKey, Object... msgArgs) { + return msgImpl(msgKey, msgArgs); + } + + /** + * Creates an I18N message using the key and arguments + * + * @param msgKey the key in the underlying message bundle + * @param msgArgs the message arguments + * + * @return the formatted I18N message + */ + public String msg(String msgKey, List msgArgs) { + return msgImpl(msgKey, msgArgs.toArray()); + } + + private String msgImpl(String msgKey, Object[] msgArgs) { String msgPattern = null; try { msgPattern = resourceBundle.getString(msgKey); } catch (MissingResourceException e) { assertShouldNeverHappen("There must be a resource bundle key called %s", msgKey); } - - String formattedMsg = new MessageFormat(msgPattern).format(msgArgs); - return formattedMsg; + return new MessageFormat(msgPattern).format(msgArgs); } } diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index 3b8b5f40f0..9db9414bc8 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableSet; import graphql.Assert; +import graphql.GraphQLContext; import graphql.Internal; import graphql.PublicApi; import graphql.execution.ValuesResolver; @@ -36,6 +37,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -54,7 +56,6 @@ import static graphql.schema.GraphQLTypeUtil.simplePrint; import static graphql.schema.GraphQLTypeUtil.unwrapAllAs; import static graphql.schema.GraphQLTypeUtil.unwrapOne; -import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY; @PublicApi public class Introspection { @@ -163,10 +164,14 @@ public enum TypeKind { Object type = environment.getSource(); if (type instanceof GraphQLArgument) { GraphQLArgument inputField = (GraphQLArgument) type; - return inputField.hasSetDefaultValue() ? printDefaultValue(inputField.getArgumentDefaultValue(), inputField.getType()) : null; + return inputField.hasSetDefaultValue() + ? printDefaultValue(inputField.getArgumentDefaultValue(), inputField.getType(), environment.getGraphQlContext(), environment.getLocale()) + : null; } else if (type instanceof GraphQLInputObjectField) { GraphQLInputObjectField inputField = (GraphQLInputObjectField) type; - return inputField.hasSetDefaultValue() ? printDefaultValue(inputField.getInputFieldDefaultValue(), inputField.getType()) : null; + return inputField.hasSetDefaultValue() + ? printDefaultValue(inputField.getInputFieldDefaultValue(), inputField.getType(), environment.getGraphQlContext(), environment.getLocale()) + : null; } return null; }); @@ -183,8 +188,8 @@ public enum TypeKind { register(__InputValue, "description", descriptionDataFetcher); } - private static String printDefaultValue(InputValueWithState inputValueWithState, GraphQLInputType type) { - return AstPrinter.printAst(ValuesResolver.valueToLiteral(DEFAULT_FIELD_VISIBILITY, inputValueWithState, type)); + private static String printDefaultValue(InputValueWithState inputValueWithState, GraphQLInputType type, GraphQLContext graphQLContext, Locale locale) { + return AstPrinter.printAst(ValuesResolver.valueToLiteral(inputValueWithState, type, graphQLContext, locale)); } diff --git a/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java b/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java index 9669a0e66d..ec6da5ee32 100644 --- a/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java +++ b/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java @@ -204,7 +204,7 @@ private GraphQLObjectType addAppliedDirectives(GraphQLObjectType originalType, G DataFetcher argValueDF = env -> { final GraphQLAppliedDirectiveArgument argument = env.getSource(); InputValueWithState value = argument.getArgumentValue(); - Node literal = ValuesResolver.valueToLiteral(value, argument.getType()); + Node literal = ValuesResolver.valueToLiteral(value, argument.getType(), env.getGraphQlContext(), env.getLocale()); return AstPrinter.printAst(literal); }; codeRegistry.dataFetcher(coordinates(objectType, "appliedDirectives"), df); diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java index cdc46a2215..6093fb0967 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedOperationFactory.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import graphql.GraphQLContext; import graphql.Internal; import graphql.collect.ImmutableKit; import graphql.execution.CoercedVariables; @@ -39,6 +40,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -77,19 +79,29 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperationW Document document, String operationName, RawVariables rawVariables) { + return createExecutableNormalizedOperationWithRawVariables(graphQLSchema, document, operationName, rawVariables, GraphQLContext.getDefault(), Locale.getDefault()); + } + + public static ExecutableNormalizedOperation createExecutableNormalizedOperationWithRawVariables(GraphQLSchema graphQLSchema, + Document document, + String operationName, + RawVariables rawVariables, + GraphQLContext graphQLContext, + Locale locale) { NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operationName); - return new ExecutableNormalizedOperationFactory().createExecutableNormalizedOperationImplWithRawVariables(graphQLSchema, getOperationResult.operationDefinition, getOperationResult.fragmentsByName, rawVariables); + return new ExecutableNormalizedOperationFactory().createExecutableNormalizedOperationImplWithRawVariables(graphQLSchema, getOperationResult.operationDefinition, getOperationResult.fragmentsByName, rawVariables, graphQLContext, locale); } private ExecutableNormalizedOperation createExecutableNormalizedOperationImplWithRawVariables(GraphQLSchema graphQLSchema, OperationDefinition operationDefinition, Map fragments, - RawVariables rawVariables - ) { + RawVariables rawVariables, + GraphQLContext graphQLContext, + Locale locale) { List variableDefinitions = operationDefinition.getVariableDefinitions(); - CoercedVariables coercedVariableValues = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, rawVariables); - Map normalizedVariableValues = ValuesResolver.getNormalizedVariableValues(graphQLSchema, variableDefinitions, rawVariables); + CoercedVariables coercedVariableValues = ValuesResolver.coerceVariableValues(graphQLSchema, variableDefinitions, rawVariables, graphQLContext, locale); + Map normalizedVariableValues = ValuesResolver.getNormalizedVariableValues(graphQLSchema, variableDefinitions, rawVariables, graphQLContext, locale); return createNormalizedQueryImpl(graphQLSchema, operationDefinition, fragments, coercedVariableValues, normalizedVariableValues); } @@ -304,7 +316,7 @@ private ExecutableNormalizedField createNF(FieldCollectorNormalizedQueryParams p String fieldName = field.getName(); GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(parameters.getGraphQLSchema(), objectTypes.iterator().next(), fieldName); - Map argumentValues = ValuesResolver.getArgumentValues(fieldDefinition.getArguments(), field.getArguments(),CoercedVariables.of(parameters.getCoercedVariableValues())); + Map argumentValues = ValuesResolver.getArgumentValues(fieldDefinition.getArguments(), field.getArguments(), CoercedVariables.of(parameters.getCoercedVariableValues()), parameters.getGraphQLContext(), parameters.getLocale()); Map normalizedArgumentValues = null; if (parameters.getNormalizedVariableValues() != null) { normalizedArgumentValues = ValuesResolver.getNormalizedArgumentValues(fieldDefinition.getArguments(), field.getArguments(), parameters.getNormalizedVariableValues()); diff --git a/src/main/java/graphql/normalized/FieldCollectorNormalizedQueryParams.java b/src/main/java/graphql/normalized/FieldCollectorNormalizedQueryParams.java index ba6004796c..f7e6675896 100644 --- a/src/main/java/graphql/normalized/FieldCollectorNormalizedQueryParams.java +++ b/src/main/java/graphql/normalized/FieldCollectorNormalizedQueryParams.java @@ -1,6 +1,7 @@ package graphql.normalized; import graphql.Assert; +import graphql.GraphQLContext; import graphql.Internal; import graphql.language.FragmentDefinition; import graphql.schema.GraphQLSchema; @@ -10,6 +11,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; @Internal @@ -18,6 +20,8 @@ public class FieldCollectorNormalizedQueryParams { private final Map fragmentsByName; private final Map coercedVariableValues; private final Map normalizedVariableValues; + private final GraphQLContext graphQLContext; + private final Locale locale; public List possibleMergerList = new ArrayList<>(); @@ -53,14 +57,21 @@ public Map getNormalizedVariableValues() { return normalizedVariableValues; } - private FieldCollectorNormalizedQueryParams(GraphQLSchema graphQLSchema, - Map coercedVariableValues, - Map normalizedVariableValues, - Map fragmentsByName) { - this.fragmentsByName = fragmentsByName; - this.graphQLSchema = graphQLSchema; - this.coercedVariableValues = coercedVariableValues; - this.normalizedVariableValues = normalizedVariableValues; + public GraphQLContext getGraphQLContext() { + return graphQLContext; + } + + public Locale getLocale() { + return locale; + } + + private FieldCollectorNormalizedQueryParams(Builder builder) { + this.fragmentsByName = builder.fragmentsByName; + this.graphQLSchema = builder.graphQLSchema; + this.coercedVariableValues = builder.coercedVariableValues; + this.normalizedVariableValues = builder.normalizedVariableValues; + this.graphQLContext = builder.graphQLContext; + this.locale = builder.locale; } public static Builder newParameters() { @@ -72,6 +83,8 @@ public static class Builder { private final Map fragmentsByName = new LinkedHashMap<>(); private final Map coercedVariableValues = new LinkedHashMap<>(); private Map normalizedVariableValues; + private GraphQLContext graphQLContext = GraphQLContext.getDefault(); + private Locale locale = Locale.getDefault(); /** * @see FieldCollectorNormalizedQueryParams#newParameters() @@ -100,9 +113,19 @@ public Builder normalizedVariables(Map normalizedV return this; } + public Builder graphQLContext(GraphQLContext graphQLContext) { + this.graphQLContext = graphQLContext; + return this; + } + + public Builder locale(Locale locale) { + this.locale = locale; + return this; + } + public FieldCollectorNormalizedQueryParams build() { Assert.assertNotNull(graphQLSchema, () -> "You must provide a schema"); - return new FieldCollectorNormalizedQueryParams(graphQLSchema, coercedVariableValues, normalizedVariableValues, fragmentsByName); + return new FieldCollectorNormalizedQueryParams(this); } } diff --git a/src/main/java/graphql/parser/Parser.java b/src/main/java/graphql/parser/Parser.java index edd0966aa6..bc6fd8e3b4 100644 --- a/src/main/java/graphql/parser/Parser.java +++ b/src/main/java/graphql/parser/Parser.java @@ -68,6 +68,10 @@ public static Document parse(String input) throws InvalidSyntaxException { return new Parser().parseDocument(input); } + public static Document parse(ParserEnvironment environment) throws InvalidSyntaxException { + return new Parser().parseDocument(environment); + } + /** * Parses a string input into a graphql AST {@link Value} * @@ -153,7 +157,10 @@ public Document parseDocument(String input, ParserOptions parserOptions) throws * @throws InvalidSyntaxException if the input is not valid graphql syntax */ public Document parseDocument(Reader reader) throws InvalidSyntaxException { - return parseDocumentImpl(reader, null); + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() + .document(reader) + .build(); + return parseDocumentImpl(parserEnvironment); } /** @@ -167,16 +174,34 @@ public Document parseDocument(Reader reader) throws InvalidSyntaxException { * @throws InvalidSyntaxException if the input is not valid graphql syntax */ public Document parseDocument(Reader reader, ParserOptions parserOptions) throws InvalidSyntaxException { - return parseDocumentImpl(reader, parserOptions); + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() + .document(reader) + .parserOptions(parserOptions) + .build(); + return parseDocumentImpl(parserEnvironment); } - private Document parseDocumentImpl(Reader reader, ParserOptions parserOptions) throws InvalidSyntaxException { + /** + * Parses document text into a graphql AST {@link Document} + * + * @param environment the parser environment to sue + * + * @return an AST {@link Document} + * + * @throws InvalidSyntaxException if the input is not valid graphql syntax + */ + public Document parseDocument(ParserEnvironment environment) throws InvalidSyntaxException { + return parseDocumentImpl(environment); + } + + + private Document parseDocumentImpl(ParserEnvironment environment) throws InvalidSyntaxException { BiFunction nodeFunction = (parser, toLanguage) -> { GraphqlParser.DocumentContext documentContext = parser.document(); Document doc = toLanguage.createDocument(documentContext); return new Object[]{documentContext, doc}; }; - return (Document) parseImpl(reader, nodeFunction, parserOptions); + return (Document) parseImpl(environment, nodeFunction); } private Value parseValueImpl(String input) throws InvalidSyntaxException { @@ -189,7 +214,8 @@ private Value parseValueImpl(String input) throws InvalidSyntaxException { .string(input, null) .trackData(true) .build(); - return (Value) parseImpl(multiSourceReader, nodeFunction, null); + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment().document(multiSourceReader).build(); + return (Value) parseImpl(parserEnvironment, nodeFunction); } private Type parseTypeImpl(String input) throws InvalidSyntaxException { @@ -202,11 +228,14 @@ private Type parseTypeImpl(String input) throws InvalidSyntaxException { .string(input, null) .trackData(true) .build(); - return (Type) parseImpl(multiSourceReader, nodeFunction, null); + + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment().document(multiSourceReader).build(); + return (Type) parseImpl(parserEnvironment, nodeFunction); } - private Node parseImpl(Reader reader, BiFunction nodeFunction, ParserOptions parserOptions) throws InvalidSyntaxException { + private Node parseImpl(ParserEnvironment environment, BiFunction nodeFunction) throws InvalidSyntaxException { MultiSourceReader multiSourceReader; + Reader reader = environment.getDocument(); if (reader instanceof MultiSourceReader) { multiSourceReader = (MultiSourceReader) reader; } else { @@ -232,6 +261,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int }); // default in the parser options if they are not set + ParserOptions parserOptions = environment.getParserOptions(); parserOptions = Optional.ofNullable(parserOptions).orElse(ParserOptions.getDefaultParserOptions()); // this lexer wrapper allows us to stop lexing when too many tokens are in place. This prevents DOS attacks. @@ -321,7 +351,7 @@ public int getCharPositionInLine() { } private void throwCancelParseIfTooManyTokens(Token token, int maxTokens, MultiSourceReader multiSourceReader) throws ParseCancelledException { - String tokenType = "grammar"; + String tokenType = "grammar"; SourceLocation sourceLocation = null; String offendingToken = null; if (token != null) { diff --git a/src/main/java/graphql/parser/ParserEnvironment.java b/src/main/java/graphql/parser/ParserEnvironment.java new file mode 100644 index 0000000000..417aa2d3dd --- /dev/null +++ b/src/main/java/graphql/parser/ParserEnvironment.java @@ -0,0 +1,87 @@ +package graphql.parser; + +import graphql.PublicApi; + +import java.io.Reader; +import java.io.StringReader; +import java.util.Locale; + +import static graphql.Assert.assertNotNull; + +/** + * This is the arguments that can be passed to a {@link Parser} + */ +@PublicApi +public interface ParserEnvironment { + + /** + * @return the document to be parsed + */ + Reader getDocument(); + + /** + * @return the parsing options + */ + ParserOptions getParserOptions(); + + /** + * @return the locale to produce parsing error messages in + */ + Locale getLocale(); + + /** + * @return a builder of new parsing options + */ + static Builder newParserEnvironment() { + return new Builder(); + } + + class Builder { + Reader reader; + ParserOptions parserOptions = ParserOptions.getDefaultParserOptions(); + + Locale locale = Locale.getDefault(); + + + public Builder() { + } + + public Builder document(Reader documentText) { + this.reader = assertNotNull(documentText); + return this; + } + + public Builder document(String documentText) { + return document(new StringReader(documentText)); + } + + public Builder parserOptions(ParserOptions parserOptions) { + this.parserOptions = parserOptions; + return this; + } + + public Builder locale(Locale locale) { + this.locale = assertNotNull(locale); + return this; + } + + public ParserEnvironment build() { + return new ParserEnvironment() { + @Override + public Reader getDocument() { + return reader; + } + + @Override + public ParserOptions getParserOptions() { + return parserOptions; + } + + @Override + public Locale getLocale() { + return locale; + } + }; + } + } +} diff --git a/src/main/java/graphql/scalar/CoercingUtil.java b/src/main/java/graphql/scalar/CoercingUtil.java index 7464d3161d..c3ac535522 100644 --- a/src/main/java/graphql/scalar/CoercingUtil.java +++ b/src/main/java/graphql/scalar/CoercingUtil.java @@ -1,18 +1,25 @@ package graphql.scalar; import graphql.Internal; +import graphql.i18n.I18n; + +import java.util.Locale; @Internal -class CoercingUtil { - static boolean isNumberIsh(Object input) { +public class CoercingUtil { + public static boolean isNumberIsh(Object input) { return input instanceof Number || input instanceof String; } - static String typeName(Object input) { + public static String typeName(Object input) { if (input == null) { return "null"; } return input.getClass().getSimpleName(); } + + public static String i18nMsg(Locale locale, String msgKey, Object... args) { + return I18n.i18n(I18n.BundleType.Scalars, locale).msg(msgKey, args); + } } diff --git a/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java b/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java index d4b01750af..266e64c4a5 100644 --- a/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java @@ -1,20 +1,30 @@ package graphql.scalar; +import graphql.GraphQLContext; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.BooleanValue; import graphql.language.Value; import graphql.schema.Coercing; import graphql.schema.CoercingParseLiteralException; import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; +import java.util.Locale; import static graphql.Assert.assertNotNull; import static graphql.Assert.assertShouldNeverHappen; +import static graphql.scalar.CoercingUtil.i18nMsg; import static graphql.scalar.CoercingUtil.isNumberIsh; import static graphql.scalar.CoercingUtil.typeName; +/** + * The deprecated methods still have implementations in case code outside graphql-java is calling them + * but internally the call paths have been replaced. + */ @Internal public class GraphqlBooleanCoercing implements Coercing { @@ -45,41 +55,84 @@ private Boolean convertImpl(Object input) { } - @Override - public Boolean serialize(Object input) { + @NotNull + private Boolean serializeImpl(@NotNull Object input, @NotNull Locale locale) { Boolean result = convertImpl(input); if (result == null) { throw new CoercingSerializeException( - "Expected type 'Boolean' but was '" + typeName(input) + "'." + i18nMsg(locale, "Boolean.notBoolean", typeName(input)) ); } return result; } - @Override - public Boolean parseValue(Object input) { + @NotNull + private Boolean parseValueImpl(@NotNull Object input, @NotNull Locale locale) { Boolean result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( - "Expected type 'Boolean' but was '" + typeName(input) + "'." + i18nMsg(locale, "Boolean.notBoolean", typeName(input)) ); } return result; } - @Override - public Boolean parseLiteral(Object input) { + private static boolean parseLiteralImpl(@NotNull Object input, @NotNull Locale locale) { if (!(input instanceof BooleanValue)) { throw new CoercingParseLiteralException( - "Expected AST type 'BooleanValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "Boolean.unexpectedAstType", typeName(input)) ); } return ((BooleanValue) input).isValue(); } - @Override - public Value valueToLiteral(Object input) { - Boolean result = assertNotNull(convertImpl(input)); + @NotNull + private BooleanValue valueToLiteralImpl(@NotNull Object input, @NotNull Locale locale) { + Boolean result = assertNotNull(convertImpl(input), () -> i18nMsg(locale, "Boolean.notBoolean", typeName(input))); return BooleanValue.newBooleanValue(result).build(); } + + @Override + @Deprecated + public Boolean serialize(@NotNull Object dataFetcherResult) { + return serializeImpl(dataFetcherResult, Locale.getDefault()); + } + + @Override + public @Nullable Boolean serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + return serializeImpl(dataFetcherResult, locale); + } + + @Override + @Deprecated + public Boolean parseValue(@NotNull Object input) { + return parseValueImpl(input, Locale.getDefault()); + } + + @Override + public Boolean parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + return parseValueImpl(input, locale); + } + + @Override + @Deprecated + public Boolean parseLiteral(@NotNull Object input) { + return parseLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @Nullable Boolean parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + return parseLiteralImpl(input, locale); + } + + @Override + @Deprecated + public Value valueToLiteral(@NotNull Object input) { + return valueToLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + return valueToLiteralImpl(input, locale); + } } diff --git a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java index c84fd538b2..83261c4e2f 100644 --- a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java @@ -1,6 +1,8 @@ package graphql.scalar; +import graphql.GraphQLContext; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.FloatValue; import graphql.language.IntValue; import graphql.language.Value; @@ -8,13 +10,21 @@ import graphql.schema.CoercingParseLiteralException; import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; +import java.util.Locale; import static graphql.Assert.assertNotNull; +import static graphql.scalar.CoercingUtil.i18nMsg; import static graphql.scalar.CoercingUtil.isNumberIsh; import static graphql.scalar.CoercingUtil.typeName; +/** + * The deprecated methods still have implementations in case code outside graphql-java is calling them + * but internally the call paths have been replaced. + */ @Internal public class GraphqlFloatCoercing implements Coercing { @@ -33,46 +43,88 @@ private Double convertImpl(Object input) { } - @Override - public Double serialize(Object input) { + @NotNull + private Double serialiseImpl(Object input, @NotNull Locale locale) { Double result = convertImpl(input); if (result == null) { throw new CoercingSerializeException( - "Expected type 'Float' but was '" + typeName(input) + "'." + i18nMsg(locale, "Float.notFloat", typeName(input)) ); } return result; - } - @Override - public Double parseValue(Object input) { + @NotNull + private Double parseValueImpl(Object input, @NotNull Locale locale) { Double result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( - "Expected type 'Float' but was '" + typeName(input) + "'." + i18nMsg(locale, "Float.notFloat", typeName(input)) ); } return result; } - @Override - public Double parseLiteral(Object input) { + private static double parseLiteralImpl(@NotNull Object input, @NotNull Locale locale) { if (input instanceof IntValue) { return ((IntValue) input).getValue().doubleValue(); } else if (input instanceof FloatValue) { return ((FloatValue) input).getValue().doubleValue(); } else { throw new CoercingParseLiteralException( - "Expected AST type 'IntValue' or 'FloatValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "Float.unexpectedAstType", typeName(input)) ); } } - @Override - public Value valueToLiteral(Object input) { - Double result = assertNotNull(convertImpl(input)); + @NotNull + private FloatValue valueToLiteralImpl(Object input, @NotNull Locale locale) { + Double result = assertNotNull(convertImpl(input), () -> i18nMsg(locale, "Float.notFloat", typeName(input))); return FloatValue.newFloatValue(BigDecimal.valueOf(result)).build(); } + + @Override + @Deprecated + public Double serialize(@NotNull Object dataFetcherResult) { + return serialiseImpl(dataFetcherResult, Locale.getDefault()); + } + + @Override + public @Nullable Double serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + return serialiseImpl(dataFetcherResult, locale); + } + + @Override + @Deprecated + public @NotNull Double parseValue(@NotNull Object input) { + return parseValueImpl(input, Locale.getDefault()); + } + + @Override + public Double parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + return parseValueImpl(input, locale); + } + + @Override + @Deprecated + public Double parseLiteral(@NotNull Object input) { + return parseLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @Nullable Double parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + return parseLiteralImpl(input, locale); + } + + @Override + @Deprecated + public Value valueToLiteral(@NotNull Object input) { + return valueToLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + return valueToLiteralImpl(input, locale); + } } diff --git a/src/main/java/graphql/scalar/GraphqlIDCoercing.java b/src/main/java/graphql/scalar/GraphqlIDCoercing.java index 69f8ed8b92..76e78e7917 100644 --- a/src/main/java/graphql/scalar/GraphqlIDCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlIDCoercing.java @@ -1,6 +1,8 @@ package graphql.scalar; +import graphql.GraphQLContext; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.IntValue; import graphql.language.StringValue; import graphql.language.Value; @@ -8,13 +10,21 @@ import graphql.schema.CoercingParseLiteralException; import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.math.BigInteger; +import java.util.Locale; import java.util.UUID; import static graphql.Assert.assertNotNull; +import static graphql.scalar.CoercingUtil.i18nMsg; import static graphql.scalar.CoercingUtil.typeName; +/** + * The deprecated methods still have implementations in case code outside graphql-java is calling them + * but internally the call paths have been replaced. + */ @Internal public class GraphqlIDCoercing implements Coercing { @@ -38,8 +48,8 @@ private String convertImpl(Object input) { } - @Override - public String serialize(Object input) { + @NotNull + private String serializeImpl(Object input, @NotNull Locale locale) { String result = String.valueOf(input); if (result == null) { throw new CoercingSerializeException( @@ -49,19 +59,18 @@ public String serialize(Object input) { return result; } - @Override - public String parseValue(Object input) { + @NotNull + private String parseValueImpl(Object input, @NotNull Locale locale) { String result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( - "Expected type 'ID' but was '" + typeName(input) + "'." + i18nMsg(locale, "ID.notId", typeName(input)) ); } return result; } - @Override - public String parseLiteral(Object input) { + private String parseLiteralImpl(Object input, @NotNull Locale locale) { if (input instanceof StringValue) { return ((StringValue) input).getValue(); } @@ -69,13 +78,57 @@ public String parseLiteral(Object input) { return ((IntValue) input).getValue().toString(); } throw new CoercingParseLiteralException( - "Expected AST type 'IntValue' or 'StringValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "ID.unexpectedAstType", typeName(input)) ); } - @Override - public Value valueToLiteral(Object input) { - String result = assertNotNull(convertImpl(input)); + @NotNull + private StringValue valueToLiteralImpl(Object input, @NotNull Locale locale) { + String result = assertNotNull(convertImpl(input), () -> i18nMsg(locale, "ID.notId", typeName(input))); return StringValue.newStringValue(result).build(); } + + @Override + @Deprecated + public String serialize(@NotNull Object dataFetcherResult) { + return serializeImpl(dataFetcherResult, Locale.getDefault()); + } + + @Override + public @Nullable Object serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + return serializeImpl(dataFetcherResult, locale); + } + + @Override + @Deprecated + public String parseValue(@NotNull Object input) { + return parseValueImpl(input, Locale.getDefault()); + } + + @Override + public Object parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + return parseValueImpl(input, locale); + } + + @Override + @Deprecated + public String parseLiteral(@NotNull Object input) { + return parseLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @Nullable Object parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + return parseLiteralImpl(input, locale); + } + + @Override + @Deprecated + public Value valueToLiteral(@NotNull Object input) { + return valueToLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + return valueToLiteralImpl(input, locale); + } } diff --git a/src/main/java/graphql/scalar/GraphqlIntCoercing.java b/src/main/java/graphql/scalar/GraphqlIntCoercing.java index a828019688..1ca0f5b2ee 100644 --- a/src/main/java/graphql/scalar/GraphqlIntCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlIntCoercing.java @@ -1,20 +1,30 @@ package graphql.scalar; +import graphql.GraphQLContext; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.IntValue; import graphql.language.Value; import graphql.schema.Coercing; import graphql.schema.CoercingParseLiteralException; import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Locale; import static graphql.Assert.assertNotNull; +import static graphql.scalar.CoercingUtil.i18nMsg; import static graphql.scalar.CoercingUtil.isNumberIsh; import static graphql.scalar.CoercingUtil.typeName; +/** + * The deprecated methods still have implementations in case code outside graphql-java is calling them + * but internally the call paths have been replaced. + */ @Internal public class GraphqlIntCoercing implements Coercing { @@ -41,47 +51,90 @@ private Integer convertImpl(Object input) { } } - @Override - public Integer serialize(Object input) { + @NotNull + private Integer serialiseImpl(Object input, @NotNull Locale locale) { Integer result = convertImpl(input); if (result == null) { throw new CoercingSerializeException( - "Expected type 'Int' but was '" + typeName(input) + "'." + i18nMsg(locale, "Int.notInt", typeName(input)) ); } return result; } - @Override - public Integer parseValue(Object input) { + @NotNull + private Integer parseValueImpl(Object input, @NotNull Locale locale) { Integer result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( - "Expected type 'Int' but was '" + typeName(input) + "'." + i18nMsg(locale, "Int.notInt", typeName(input)) ); } return result; } - @Override - public Integer parseLiteral(Object input) { + private static int parseLiteralImpl(Object input, @NotNull Locale locale) { if (!(input instanceof IntValue)) { throw new CoercingParseLiteralException( - "Expected AST type 'IntValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "Scalar.unexpectedAstType", "IntValue", typeName(input)) ); } BigInteger value = ((IntValue) input).getValue(); if (value.compareTo(INT_MIN) < 0 || value.compareTo(INT_MAX) > 0) { throw new CoercingParseLiteralException( - "Expected value to be in the Integer range but it was '" + value.toString() + "'" + i18nMsg(locale, "Int.outsideRange", value.toString()) ); } return value.intValue(); } - @Override - public Value valueToLiteral(Object input) { - Integer result = assertNotNull(convertImpl(input)); + private IntValue valueToLiteralImpl(Object input, @NotNull Locale locale) { + Integer result = assertNotNull(convertImpl(input),() -> i18nMsg(locale, "Int.notInt", typeName(input))); return IntValue.newIntValue(BigInteger.valueOf(result)).build(); } + + + @Override + @Deprecated + public Integer serialize(@NotNull Object dataFetcherResult) { + return serialiseImpl(dataFetcherResult, Locale.getDefault()); + } + + @Override + public @Nullable Integer serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + return serialiseImpl(dataFetcherResult, locale); + } + + @Override + @Deprecated + public Integer parseValue(@NotNull Object input) { + return parseValueImpl(input, Locale.getDefault()); + } + + @Override + public Integer parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + return parseValueImpl(input, locale); + } + + @Override + @Deprecated + public Integer parseLiteral(@NotNull Object input) { + return parseLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @Nullable Integer parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + return parseLiteralImpl(input, locale); + } + + @Override + @Deprecated + public Value valueToLiteral(@NotNull Object input) { + return valueToLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + return valueToLiteralImpl(input, locale); + } } diff --git a/src/main/java/graphql/scalar/GraphqlStringCoercing.java b/src/main/java/graphql/scalar/GraphqlStringCoercing.java index 7bddeba04d..541d157fd9 100644 --- a/src/main/java/graphql/scalar/GraphqlStringCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlStringCoercing.java @@ -1,37 +1,88 @@ package graphql.scalar; +import graphql.GraphQLContext; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.StringValue; import graphql.language.Value; import graphql.schema.Coercing; import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Locale; + +import static graphql.scalar.CoercingUtil.i18nMsg; import static graphql.scalar.CoercingUtil.typeName; +/** + * The deprecated methods still have implementations in case code outside graphql-java is calling them + * but internally the call paths have been replaced. + */ @Internal public class GraphqlStringCoercing implements Coercing { - @Override - public String serialize(Object input) { - return input.toString(); - } - @Override - public String parseValue(Object input) { - return serialize(input); + + private String toStringImpl(Object input) { + return String.valueOf(input); } - @Override - public String parseLiteral(Object input) { + private String parseLiteralImpl(@NotNull Object input, Locale locale) { if (!(input instanceof StringValue)) { throw new CoercingParseLiteralException( - "Expected AST type 'StringValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "Scalar.unexpectedAstType", "StringValue", typeName(input)) ); } return ((StringValue) input).getValue(); } - @Override - public Value valueToLiteral(Object input) { + private StringValue valueToLiteralImpl(@NotNull Object input) { return StringValue.newStringValue(input.toString()).build(); } + + @Override + @Deprecated + public String serialize(@NotNull Object dataFetcherResult) { + return toStringImpl(dataFetcherResult); + } + + @Override + public @Nullable String serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + return toStringImpl(dataFetcherResult); + } + + @Override + @Deprecated + public String parseValue(@NotNull Object input) { + return toStringImpl(input); + } + + @Override + public String parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + return toStringImpl(input); + } + + @Override + @Deprecated + public String parseLiteral(@NotNull Object input) { + return parseLiteralImpl(input, Locale.getDefault()); + } + + @Override + public @Nullable String parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + return parseLiteralImpl(input, locale); + } + + @Override + @Deprecated + public Value valueToLiteral(@NotNull Object input) { + return valueToLiteralImpl(input); + } + + @Override + public @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + return valueToLiteralImpl(input); + } } diff --git a/src/main/java/graphql/schema/Coercing.java b/src/main/java/graphql/schema/Coercing.java index 79b62b959c..669242d2f9 100644 --- a/src/main/java/graphql/schema/Coercing.java +++ b/src/main/java/graphql/schema/Coercing.java @@ -1,12 +1,18 @@ package graphql.schema; +import graphql.GraphQLContext; import graphql.PublicSpi; +import graphql.execution.CoercedVariables; import graphql.language.Value; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Locale; import java.util.Map; +import static graphql.Assert.assertNotNull; + /** * The Coercing interface is used by {@link graphql.schema.GraphQLScalarType}s to parse and serialise object values. *

@@ -45,7 +51,32 @@ public interface Coercing { * * @throws graphql.schema.CoercingSerializeException if value input can't be serialized */ - O serialize(@NotNull Object dataFetcherResult) throws CoercingSerializeException; + @Deprecated + @Nullable O serialize(@NotNull Object dataFetcherResult) throws CoercingSerializeException; + + /** + * Called to convert a Java object result of a DataFetcher to a valid runtime value for the scalar type. + *

+ * Note : Throw {@link graphql.schema.CoercingSerializeException} if there is fundamental + * problem during serialisation, don't return null to indicate failure. + *

+ * Note : You should not allow {@link java.lang.RuntimeException}s to come out of your serialize method, but rather + * catch them and fire them as {@link graphql.schema.CoercingSerializeException} instead as per the method contract. + * + * @param dataFetcherResult is never null + * @param graphQLContext the graphql context in place + * @param locale the locale to use + * + * @return a serialized value which may be null. + * + * @throws graphql.schema.CoercingSerializeException if value input can't be serialized + */ + default @Nullable O serialize(@NotNull Object dataFetcherResult, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingSerializeException { + assertNotNull(dataFetcherResult); + assertNotNull(graphQLContext); + return serialize(dataFetcherResult); + } + /** * Called to resolve an input from a query variable into a Java object acceptable for the scalar type. @@ -59,8 +90,30 @@ public interface Coercing { * * @throws graphql.schema.CoercingParseValueException if value input can't be parsed */ + @Deprecated @NotNull I parseValue(@NotNull Object input) throws CoercingParseValueException; + /** + * Called to resolve an input from a query variable into a Java object acceptable for the scalar type. + *

+ * Note : You should not allow {@link java.lang.RuntimeException}s to come out of your parseValue method, but rather + * catch them and fire them as {@link graphql.schema.CoercingParseValueException} instead as per the method contract. + * + * @param input is never null + * @param graphQLContext the graphql context in place + * @param locale the locale to use + * + * @return a parsed value which is never null + * + * @throws graphql.schema.CoercingParseValueException if value input can't be parsed + */ + default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + assertNotNull(input); + assertNotNull(graphQLContext); + assertNotNull(locale); + return parseValue(input); + } + /** * Called during query validation to convert a query input AST node into a Java object acceptable for the scalar type. The input * object will be an instance of {@link graphql.language.Value}. @@ -74,7 +127,8 @@ public interface Coercing { * * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed */ - @NotNull I parseLiteral(@NotNull Object input) throws CoercingParseLiteralException; + @Deprecated + @Nullable I parseLiteral(@NotNull Object input) throws CoercingParseLiteralException; /** * Called during query execution to convert a query input AST node into a Java object acceptable for the scalar type. The input @@ -95,21 +149,67 @@ public interface Coercing { * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed */ @SuppressWarnings("unused") - default @NotNull I parseLiteral(Object input, Map variables) throws CoercingParseLiteralException { + @Deprecated + default @Nullable I parseLiteral(Object input, Map variables) throws CoercingParseLiteralException { return parseLiteral(input); } + /** + * Called during query execution to convert a query input AST node into a Java object acceptable for the scalar type. The input + * object will be an instance of {@link graphql.language.Value}. + *

+ * Note : You should not allow {@link java.lang.RuntimeException}s to come out of your parseLiteral method, but rather + * catch them and fire them as {@link graphql.schema.CoercingParseLiteralException} instead as per the method contract. + *

+ * Many scalar types don't need to implement this method because they don't take AST {@link graphql.language.VariableReference} + * objects and convert them into actual values. But for those scalar types that want to do this, then this + * method should be implemented. + * + * @param input is never null + * @param variables the resolved variables passed to the query + * @param graphQLContext the graphql context in place + * @param locale the locale to use + * + * @return a parsed value which may be null, say for {@link graphql.language.NullValue} as input + * + * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed + */ + default @Nullable I parseLiteral(@NotNull Value input, @NotNull CoercedVariables variables, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseLiteralException { + assertNotNull(input); + assertNotNull(graphQLContext); + assertNotNull(locale); + return parseLiteral(input, variables.toMap()); + } + /** * Converts an external input value to a literal (Ast Value). - * + *

* IMPORTANT: the argument is validated before by calling {@link #parseValue(Object)}. * * @param input an external input value * * @return The literal matching the external input value. */ + @Deprecated default @NotNull Value valueToLiteral(@NotNull Object input) { throw new UnsupportedOperationException("This is not implemented by this Scalar " + this.getClass()); } + + /** + * Converts an external input value to a literal (Ast Value). + *

+ * IMPORTANT: the argument is validated before by calling {@link #parseValue(Object)}. + * + * @param input an external input value + * @param graphQLContext the graphql context in place + * + * @return The literal matching the external input value. + */ + default @NotNull Value valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) { + assertNotNull(input); + assertNotNull(graphQLContext); + assertNotNull(locale); + return valueToLiteral(input); + } } diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java index dd595a5392..f2fca9b8e7 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java @@ -2,6 +2,7 @@ import graphql.Assert; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.collect.ImmutableKit; import graphql.language.Argument; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; import static graphql.Assert.assertNotNull; @@ -86,7 +88,7 @@ public boolean hasSetValue() { * @return a value of type T which is the java value of the argument */ public T getValue() { - return getInputValueImpl(getType(), value); + return getInputValueImpl(getType(), value, GraphQLContext.getDefault(), Locale.getDefault()); } /** diff --git a/src/main/java/graphql/schema/GraphQLArgument.java b/src/main/java/graphql/schema/GraphQLArgument.java index 74c8477274..eb4f156ed8 100644 --- a/src/main/java/graphql/schema/GraphQLArgument.java +++ b/src/main/java/graphql/schema/GraphQLArgument.java @@ -3,6 +3,7 @@ import graphql.DeprecatedAt; import graphql.DirectivesUtil; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.language.InputValueDefinition; import graphql.language.Value; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -150,7 +152,7 @@ public boolean hasSetValue() { @Deprecated @DeprecatedAt("2022-02-24") public static T getArgumentValue(GraphQLArgument argument) { - return getInputValueImpl(argument.getType(), argument.getArgumentValue()); + return getInputValueImpl(argument.getType(), argument.getArgumentValue(), GraphQLContext.getDefault(), Locale.getDefault()); } /** @@ -170,7 +172,7 @@ public static T getArgumentValue(GraphQLArgument argument) { * @return a value of type T which is the java value of the argument default */ public static T getArgumentDefaultValue(GraphQLArgument argument) { - return getInputValueImpl(argument.getType(), argument.getArgumentDefaultValue()); + return getInputValueImpl(argument.getType(), argument.getArgumentDefaultValue(), GraphQLContext.getDefault(), Locale.getDefault()); } public String getDescription() { diff --git a/src/main/java/graphql/schema/GraphQLEnumType.java b/src/main/java/graphql/schema/GraphQLEnumType.java index 9dc553a080..27354e1e74 100644 --- a/src/main/java/graphql/schema/GraphQLEnumType.java +++ b/src/main/java/graphql/schema/GraphQLEnumType.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import graphql.DirectivesUtil; +import graphql.GraphQLContext; import graphql.Internal; import graphql.PublicApi; import graphql.language.EnumTypeDefinition; @@ -14,9 +15,11 @@ import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -24,6 +27,8 @@ import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertValidName; import static graphql.collect.ImmutableKit.emptyList; +import static graphql.scalar.CoercingUtil.i18nMsg; +import static graphql.scalar.CoercingUtil.typeName; import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition; import static graphql.util.FpKit.getByName; @@ -67,43 +72,66 @@ private GraphQLEnumType(String name, } @Internal + @Deprecated public Object serialize(Object input) { - return getNameByValue(input); + return serialize(input, GraphQLContext.getDefault(), Locale.getDefault()); } @Internal + public Object serialize(Object input, GraphQLContext graphQLContext, Locale locale) { + return getNameByValue(input, graphQLContext, locale); + } + + @Internal + @Deprecated public Object parseValue(Object input) { - return getValueByName(input); + return getValueByName(input, GraphQLContext.getDefault(), Locale.getDefault()); } - private String typeName(Object input) { - if (input == null) { - return "null"; - } - return input.getClass().getSimpleName(); + @Internal + public Object parseValue(Object input, GraphQLContext graphQLContext, Locale locale) { + return getValueByName(input, graphQLContext, locale); } + @Internal + @Deprecated public Object parseLiteral(Object input) { + return parseLiteralImpl(input, GraphQLContext.getDefault(), Locale.getDefault()); + } + + @Internal + public Object parseLiteral(Value input, GraphQLContext graphQLContext, Locale locale) { + return parseLiteralImpl(input, graphQLContext, locale); + } + + private Object parseLiteralImpl(Object input, GraphQLContext graphQLContext, Locale locale) { if (!(input instanceof EnumValue)) { throw new CoercingParseLiteralException( - "Expected AST type 'EnumValue' but was '" + typeName(input) + "'." + i18nMsg(locale, "Scalar.unexpectedAstType", "EnumValue", typeName(input)) ); } EnumValue enumValue = (EnumValue) input; GraphQLEnumValueDefinition enumValueDefinition = valueDefinitionMap.get(enumValue.getName()); if (enumValueDefinition == null) { throw new CoercingParseLiteralException( - "Expected enum literal value not in allowable values - '" + input + "'." + i18nMsg(locale, "Enum.unallowableValue", getName(), input) ); } return enumValueDefinition.getValue(); } + @Internal + @Deprecated public Value valueToLiteral(Object input) { + return valueToLiteral(input, GraphQLContext.getDefault(), Locale.getDefault()); + } + + @Internal + public Value valueToLiteral(Object input, GraphQLContext graphQLContext, Locale locale) { GraphQLEnumValueDefinition enumValueDefinition = valueDefinitionMap.get(input.toString()); - assertNotNull(enumValueDefinition, () -> "Invalid input for Enum '" + name + "'. No value found for name '" + input + "'"); + assertNotNull(enumValueDefinition, () -> i18nMsg(locale, "Enum.badName", name, input.toString())); return EnumValue.newEnumValue(enumValueDefinition.getName()).build(); } @@ -121,15 +149,15 @@ private ImmutableMap buildMap(List assertShouldNeverHappen("Duplicated definition for field '%s' in type '%s'", fld1.getName(), this.name))); } - private Object getValueByName(Object value) { + private Object getValueByName(@Nonnull Object value, GraphQLContext graphQLContext, Locale locale) { GraphQLEnumValueDefinition enumValueDefinition = valueDefinitionMap.get(value.toString()); if (enumValueDefinition != null) { return enumValueDefinition.getValue(); } - throw new CoercingParseValueException("Invalid input for Enum '" + name + "'. No value found for name '" + value.toString() + "'"); + throw new CoercingParseValueException(i18nMsg(locale, "Enum.badName", name, value.toString())); } - private Object getNameByValue(Object value) { + private Object getNameByValue(Object value, GraphQLContext graphQLContext, Locale locale) { for (GraphQLEnumValueDefinition valueDefinition : valueDefinitionMap.values()) { Object definitionValue = valueDefinition.getValue(); if (value.equals(definitionValue)) { @@ -142,7 +170,7 @@ private Object getNameByValue(Object value) { } } } - // ok we didn't match on pure object.equals(). Lets try the Java enum strategy + // ok we didn't match on pure object.equals(). Let's try the Java enum strategy if (value instanceof Enum) { String enumNameValue = ((Enum) value).name(); for (GraphQLEnumValueDefinition valueDefinition : valueDefinitionMap.values()) { @@ -152,7 +180,7 @@ private Object getNameByValue(Object value) { } } } - throw new CoercingSerializeException("Invalid input for Enum '" + name + "'. Unknown value '" + value + "'"); + throw new CoercingSerializeException(i18nMsg(locale, "Enum.badInput", name, value)); } @Override diff --git a/src/main/java/graphql/schema/GraphQLInputObjectField.java b/src/main/java/graphql/schema/GraphQLInputObjectField.java index f97c6aca65..0e88a907c1 100644 --- a/src/main/java/graphql/schema/GraphQLInputObjectField.java +++ b/src/main/java/graphql/schema/GraphQLInputObjectField.java @@ -3,6 +3,7 @@ import graphql.DeprecatedAt; import graphql.DirectivesUtil; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.language.InputValueDefinition; import graphql.language.Value; @@ -12,6 +13,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -107,7 +109,7 @@ public GraphQLInputType getType() { * @return a value of type T which is the java value of the input field default */ public static T getInputFieldDefaultValue(GraphQLInputObjectField inputObjectField) { - return getInputValueImpl(inputObjectField.getType(), inputObjectField.getInputFieldDefaultValue()); + return getInputValueImpl(inputObjectField.getType(), inputObjectField.getInputFieldDefaultValue(), GraphQLContext.getDefault(), Locale.getDefault()); } diff --git a/src/main/java/graphql/schema/idl/ArgValueOfAllowedTypeChecker.java b/src/main/java/graphql/schema/idl/ArgValueOfAllowedTypeChecker.java index 83b96fad0c..c9d2498abd 100644 --- a/src/main/java/graphql/schema/idl/ArgValueOfAllowedTypeChecker.java +++ b/src/main/java/graphql/schema/idl/ArgValueOfAllowedTypeChecker.java @@ -1,8 +1,10 @@ package graphql.schema.idl; import graphql.AssertException; +import graphql.GraphQLContext; import graphql.GraphQLError; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.Argument; import graphql.language.ArrayValue; import graphql.language.Directive; @@ -32,6 +34,7 @@ import org.slf4j.Logger; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Function; import java.util.stream.Stream; @@ -285,7 +288,7 @@ private void checkArgValueMatchesAllowedListType(List errors, Valu private boolean isArgumentValueScalarLiteral(GraphQLScalarType scalarType, Value instanceValue) { try { - scalarType.getCoercing().parseLiteral(instanceValue); + scalarType.getCoercing().parseLiteral(instanceValue, CoercedVariables.emptyVariables(), GraphQLContext.getDefault(), Locale.getDefault()); return true; } catch (CoercingParseLiteralException ex) { if (logNotSafe.isDebugEnabled()) { diff --git a/src/main/java/graphql/schema/idl/SchemaPrinter.java b/src/main/java/graphql/schema/idl/SchemaPrinter.java index 3683a0ddab..e4ae7a7c63 100644 --- a/src/main/java/graphql/schema/idl/SchemaPrinter.java +++ b/src/main/java/graphql/schema/idl/SchemaPrinter.java @@ -2,6 +2,7 @@ import graphql.Assert; import graphql.DirectivesUtil; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.execution.ValuesResolver; import graphql.language.AstPrinter; @@ -53,6 +54,7 @@ import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -713,7 +715,7 @@ private void printAsAst(PrintWriter out, TypeDefinition definition, List schemaPrinter() { diff --git a/src/main/java/graphql/schema/validation/AppliedDirectiveArgumentsAreValid.java b/src/main/java/graphql/schema/validation/AppliedDirectiveArgumentsAreValid.java index af9beb9aab..82e59e2785 100644 --- a/src/main/java/graphql/schema/validation/AppliedDirectiveArgumentsAreValid.java +++ b/src/main/java/graphql/schema/validation/AppliedDirectiveArgumentsAreValid.java @@ -1,5 +1,6 @@ package graphql.schema.validation; +import graphql.GraphQLContext; import graphql.Internal; import graphql.execution.NonNullableValueCoercedAsNullException; import graphql.execution.ValuesResolver; @@ -16,6 +17,8 @@ import graphql.util.TraverserContext; import graphql.validation.ValidationUtil; +import java.util.Locale; + import static java.lang.String.format; @Internal @@ -44,10 +47,10 @@ private void checkArgument(GraphQLDirective directive, GraphQLArgument argument, InputValueWithState argumentValue = argument.getArgumentValue(); boolean invalid = false; if (argumentValue.isLiteral() && - !validationUtil.isValidLiteralValue((Value) argumentValue.getValue(), argument.getType(), schema)) { + !validationUtil.isValidLiteralValue((Value) argumentValue.getValue(), argument.getType(), schema, GraphQLContext.getDefault(), Locale.getDefault())) { invalid = true; } else if (argumentValue.isExternal() && - !isValidExternalValue(schema, argumentValue.getValue(), argument.getType())) { + !isValidExternalValue(schema, argumentValue.getValue(), argument.getType(), GraphQLContext.getDefault(), Locale.getDefault())) { invalid = true; } if (invalid) { @@ -56,9 +59,9 @@ private void checkArgument(GraphQLDirective directive, GraphQLArgument argument, } } - private boolean isValidExternalValue(GraphQLSchema schema, Object externalValue, GraphQLInputType type) { + private boolean isValidExternalValue(GraphQLSchema schema, Object externalValue, GraphQLInputType type, GraphQLContext graphQLContext, Locale locale) { try { - ValuesResolver.externalValueToInternalValue(schema.getCodeRegistry().getFieldVisibility(), externalValue, type); + ValuesResolver.externalValueToInternalValue(schema.getCodeRegistry().getFieldVisibility(), externalValue, type, graphQLContext, locale); return true; } catch (CoercingParseValueException | NonNullableValueCoercedAsNullException e) { return false; diff --git a/src/main/java/graphql/schema/validation/DefaultValuesAreValid.java b/src/main/java/graphql/schema/validation/DefaultValuesAreValid.java index ad1a15b021..4527973461 100644 --- a/src/main/java/graphql/schema/validation/DefaultValuesAreValid.java +++ b/src/main/java/graphql/schema/validation/DefaultValuesAreValid.java @@ -1,5 +1,6 @@ package graphql.schema.validation; +import graphql.GraphQLContext; import graphql.execution.NonNullableValueCoercedAsNullException; import graphql.execution.ValuesResolver; import graphql.language.Value; @@ -16,6 +17,8 @@ import graphql.util.TraverserContext; import graphql.validation.ValidationUtil; +import java.util.Locale; + import static graphql.schema.GraphQLTypeUtil.simplePrint; import static java.lang.String.format; @@ -23,6 +26,9 @@ public class DefaultValuesAreValid extends GraphQLTypeVisitorStub { private ValidationUtil validationUtil = new ValidationUtil(); + private final GraphQLContext graphQLContext = GraphQLContext.getDefault(); + + @Override public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { return super.visitGraphQLInputObjectType(node, context); @@ -38,10 +44,10 @@ public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField inp InputValueWithState defaultValue = inputObjectField.getInputFieldDefaultValue(); boolean invalid = false; if (defaultValue.isLiteral() && - !validationUtil.isValidLiteralValue((Value) defaultValue.getValue(), inputObjectField.getType(), schema)) { + !validationUtil.isValidLiteralValue((Value) defaultValue.getValue(), inputObjectField.getType(), schema, graphQLContext, Locale.getDefault())) { invalid = true; } else if (defaultValue.isExternal() && - !isValidExternalValue(schema, defaultValue.getValue(), inputObjectField.getType())) { + !isValidExternalValue(schema, defaultValue.getValue(), inputObjectField.getType(), graphQLContext)) { invalid = true; } if (invalid) { @@ -61,10 +67,10 @@ public TraversalControl visitGraphQLArgument(GraphQLArgument argument, Traverser InputValueWithState defaultValue = argument.getArgumentDefaultValue(); boolean invalid = false; if (defaultValue.isLiteral() && - !validationUtil.isValidLiteralValue((Value) defaultValue.getValue(), argument.getType(), schema)) { + !validationUtil.isValidLiteralValue((Value) defaultValue.getValue(), argument.getType(), schema, graphQLContext, Locale.getDefault())) { invalid = true; } else if (defaultValue.isExternal() && - !isValidExternalValue(schema, defaultValue.getValue(), argument.getType())) { + !isValidExternalValue(schema, defaultValue.getValue(), argument.getType(), graphQLContext)) { invalid = true; } if (invalid) { @@ -74,9 +80,9 @@ public TraversalControl visitGraphQLArgument(GraphQLArgument argument, Traverser return TraversalControl.CONTINUE; } - private boolean isValidExternalValue(GraphQLSchema schema, Object externalValue, GraphQLInputType type) { + private boolean isValidExternalValue(GraphQLSchema schema, Object externalValue, GraphQLInputType type, GraphQLContext graphQLContext) { try { - ValuesResolver.externalValueToInternalValue(schema.getCodeRegistry().getFieldVisibility(), externalValue, type); + ValuesResolver.externalValueToInternalValue(schema.getCodeRegistry().getFieldVisibility(), externalValue, type, graphQLContext, Locale.getDefault()); return true; } catch (CoercingParseValueException | NonNullableValueCoercedAsNullException e) { return false; diff --git a/src/main/java/graphql/schema/validation/TypesImplementInterfaces.java b/src/main/java/graphql/schema/validation/TypesImplementInterfaces.java index f7ec36169f..2717ed8e46 100644 --- a/src/main/java/graphql/schema/validation/TypesImplementInterfaces.java +++ b/src/main/java/graphql/schema/validation/TypesImplementInterfaces.java @@ -1,5 +1,6 @@ package graphql.schema.validation; +import graphql.GraphQLContext; import graphql.Internal; import graphql.execution.ValuesResolver; import graphql.language.Value; @@ -20,6 +21,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -151,8 +153,8 @@ private void checkFieldArgumentEquivalence(GraphQLImplementingType implementingT same = false; } if (objectArg.hasSetDefaultValue() && interfaceArg.hasSetDefaultValue()) { - Value objectDefaultValue = ValuesResolver.valueToLiteral(objectArg.getArgumentDefaultValue(), objectArg.getType()); - Value interfaceDefaultValue = ValuesResolver.valueToLiteral(interfaceArg.getArgumentDefaultValue(), interfaceArg.getType()); + Value objectDefaultValue = ValuesResolver.valueToLiteral(objectArg.getArgumentDefaultValue(), objectArg.getType(), GraphQLContext.getDefault(), Locale.getDefault()); + Value interfaceDefaultValue = ValuesResolver.valueToLiteral(interfaceArg.getArgumentDefaultValue(), interfaceArg.getType(), GraphQLContext.getDefault(), Locale.getDefault()); if (!Objects.equals(printAst(objectDefaultValue), printAst(interfaceDefaultValue))) { same = false; } diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index bb5fc69d81..a797535f0e 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -2,6 +2,7 @@ import graphql.AssertException; import graphql.Directives; +import graphql.GraphQLContext; import graphql.PublicApi; import graphql.Scalars; import graphql.analysis.QueryTraverser; @@ -42,8 +43,8 @@ import graphql.language.VariableDefinition; import graphql.language.VariableReference; import graphql.parser.Parser; -import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLDirective; @@ -78,11 +79,11 @@ import java.math.BigInteger; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -184,12 +185,12 @@ public TraversalControl visitGraphQLArgument(GraphQLArgument graphQLArgument, Tr GraphQLArgument newElement = graphQLArgument.transform(builder -> { builder.name(newName).description(null).definition(null); if (graphQLArgument.hasSetDefaultValue()) { - Value defaultValueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType()); + Value defaultValueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault()); builder.defaultValueLiteral(replaceValue(defaultValueLiteral, graphQLArgument.getType(), newNameMap, defaultStringValueCounter, defaultIntValueCounter)); } if (graphQLArgument.hasSetValue()) { - Value valueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentValue(), graphQLArgument.getType()); + Value valueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault()); builder.valueLiteral(replaceValue(valueLiteral, graphQLArgument.getType(), newNameMap, defaultStringValueCounter, defaultIntValueCounter)); } }); @@ -205,7 +206,7 @@ public TraversalControl visitGraphQLAppliedDirectiveArgument(GraphQLAppliedDirec GraphQLAppliedDirectiveArgument newElement = graphQLArgument.transform(builder -> { builder.name(newName).description(null).definition(null); if (graphQLArgument.hasSetValue()) { - Value valueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentValue(), graphQLArgument.getType()); + Value valueLiteral = ValuesResolver.valueToLiteral(graphQLArgument.getArgumentValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault()); builder.valueLiteral(replaceValue(valueLiteral, graphQLArgument.getType(), newNameMap, defaultStringValueCounter, defaultIntValueCounter)); } }); @@ -307,7 +308,7 @@ public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField gra Value defaultValue = null; if (graphQLInputObjectField.hasSetDefaultValue()) { - defaultValue = ValuesResolver.valueToLiteral(graphQLInputObjectField.getInputFieldDefaultValue(), graphQLInputObjectField.getType()); + defaultValue = ValuesResolver.valueToLiteral(graphQLInputObjectField.getInputFieldDefaultValue(), graphQLInputObjectField.getType(), GraphQLContext.getDefault(), Locale.getDefault()); defaultValue = replaceValue(defaultValue, graphQLInputObjectField.getType(), newNameMap, defaultStringValueCounter, defaultIntValueCounter); } diff --git a/src/main/java/graphql/validation/ValidationContext.java b/src/main/java/graphql/validation/ValidationContext.java index ad94f603ee..2646afcdbb 100644 --- a/src/main/java/graphql/validation/ValidationContext.java +++ b/src/main/java/graphql/validation/ValidationContext.java @@ -1,6 +1,7 @@ package graphql.validation; +import graphql.GraphQLContext; import graphql.Internal; import graphql.i18n.I18n; import graphql.language.Definition; @@ -16,6 +17,7 @@ import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; @Internal @@ -27,12 +29,14 @@ public class ValidationContext { private final TraversalContext traversalContext; private final Map fragmentDefinitionMap = new LinkedHashMap<>(); private final I18n i18n; + private final GraphQLContext graphQLContext; public ValidationContext(GraphQLSchema schema, Document document, I18n i18n) { this.schema = schema; this.document = document; this.traversalContext = new TraversalContext(schema); this.i18n = i18n; + this.graphQLContext = GraphQLContext.newContext().of(Locale.class, i18n.getLocale()).build(); buildFragmentMap(); } @@ -92,6 +96,10 @@ public I18n getI18n() { return i18n; } + public GraphQLContext getGraphQLContext() { + return graphQLContext; + } + /** * Creates an I18N message using the key and arguments * diff --git a/src/main/java/graphql/validation/ValidationUtil.java b/src/main/java/graphql/validation/ValidationUtil.java index a1bbe00ccb..413b5887fc 100644 --- a/src/main/java/graphql/validation/ValidationUtil.java +++ b/src/main/java/graphql/validation/ValidationUtil.java @@ -3,8 +3,10 @@ import com.google.common.collect.ImmutableSet; import graphql.Assert; +import graphql.GraphQLContext; import graphql.GraphQLError; import graphql.Internal; +import graphql.execution.CoercedVariables; import graphql.language.ArrayValue; import graphql.language.ListType; import graphql.language.NonNullType; @@ -28,6 +30,7 @@ import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -74,7 +77,7 @@ protected void handleFieldNotValidError(ObjectField objectField, GraphQLInputObj protected void handleFieldNotValidError(Value value, GraphQLType type, int index) { } - public boolean isValidLiteralValue(Value value, GraphQLType type, GraphQLSchema schema) { + public boolean isValidLiteralValue(Value value, GraphQLType type, GraphQLSchema schema, GraphQLContext graphQLContext, Locale locale) { if (value == null || value instanceof NullValue) { boolean valid = !(isNonNull(type)); if (!valid) { @@ -86,46 +89,46 @@ public boolean isValidLiteralValue(Value value, GraphQLType type, GraphQLSche return true; } if (isNonNull(type)) { - return isValidLiteralValue(value, unwrapOne(type), schema); + return isValidLiteralValue(value, unwrapOne(type), schema, graphQLContext, locale); } if (type instanceof GraphQLScalarType) { - Optional invalid = parseLiteral(value, ((GraphQLScalarType) type).getCoercing()); + Optional invalid = parseLiteral(value, ((GraphQLScalarType) type).getCoercing(), graphQLContext, locale); invalid.ifPresent(graphQLError -> handleScalarError(value, (GraphQLScalarType) type, graphQLError)); return !invalid.isPresent(); } if (type instanceof GraphQLEnumType) { - Optional invalid = parseLiteralEnum(value, (GraphQLEnumType) type); + Optional invalid = parseLiteralEnum(value, (GraphQLEnumType) type, graphQLContext, locale); invalid.ifPresent(graphQLError -> handleEnumError(value, (GraphQLEnumType) type, graphQLError)); return !invalid.isPresent(); } if (isList(type)) { - return isValidLiteralValue(value, (GraphQLList) type, schema); + return isValidLiteralValue(value, (GraphQLList) type, schema, graphQLContext, locale); } - return type instanceof GraphQLInputObjectType && isValidLiteralValue(value, (GraphQLInputObjectType) type, schema); + return type instanceof GraphQLInputObjectType && isValidLiteralValue(value, (GraphQLInputObjectType) type, schema, graphQLContext, locale); } - private Optional parseLiteralEnum(Value value, GraphQLEnumType graphQLEnumType) { + private Optional parseLiteralEnum(Value value, GraphQLEnumType graphQLEnumType, GraphQLContext graphQLContext, Locale locale) { try { - graphQLEnumType.parseLiteral(value); + graphQLEnumType.parseLiteral(value, graphQLContext, locale); return Optional.empty(); } catch (CoercingParseLiteralException e) { return Optional.of(e); } } - private Optional parseLiteral(Value value, Coercing coercing) { + private Optional parseLiteral(Value value, Coercing coercing, GraphQLContext graphQLContext, Locale locale) { try { - coercing.parseLiteral(value); + coercing.parseLiteral(value, CoercedVariables.emptyVariables(), graphQLContext, locale); return Optional.empty(); } catch (CoercingParseLiteralException e) { return Optional.of(e); } } - private boolean isValidLiteralValue(Value value, GraphQLInputObjectType type, GraphQLSchema schema) { + boolean isValidLiteralValue(Value value, GraphQLInputObjectType type, GraphQLSchema schema, GraphQLContext graphQLContext, Locale locale) { if (!(value instanceof ObjectValue)) { handleNotObjectError(value, type); return false; @@ -147,7 +150,7 @@ private boolean isValidLiteralValue(Value value, GraphQLInputObjectType type, handleExtraFieldError(value, type, objectField); return false; } - if (!isValidLiteralValue(objectField.getValue(), inputObjectField.getType(), schema)) { + if (!isValidLiteralValue(objectField.getValue(), inputObjectField.getType(), schema, graphQLContext, locale)) { handleFieldNotValidError(objectField, type); return false; } @@ -172,19 +175,19 @@ private Map fieldMap(ObjectValue objectValue) { return result; } - private boolean isValidLiteralValue(Value value, GraphQLList type, GraphQLSchema schema) { + private boolean isValidLiteralValue(Value value, GraphQLList type, GraphQLSchema schema, GraphQLContext graphQLContext, Locale locale) { GraphQLType wrappedType = type.getWrappedType(); if (value instanceof ArrayValue) { List values = ((ArrayValue) value).getValues(); for (int i = 0; i < values.size(); i++) { - if (!isValidLiteralValue(values.get(i), wrappedType, schema)) { + if (!isValidLiteralValue(values.get(i), wrappedType, schema, graphQLContext, locale)) { handleFieldNotValidError(values.get(i), wrappedType, i); return false; } } return true; } else { - return isValidLiteralValue(value, wrappedType, schema); + return isValidLiteralValue(value, wrappedType, schema, graphQLContext, locale); } } diff --git a/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java b/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java index 0f942bdfc8..21e85a2129 100644 --- a/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java +++ b/src/main/java/graphql/validation/rules/ArgumentsOfCorrectType.java @@ -10,6 +10,8 @@ import graphql.validation.ValidationError; import graphql.validation.ValidationErrorCollector; +import java.util.Locale; + import static graphql.validation.ValidationErrorType.WrongType; @Internal @@ -26,7 +28,7 @@ public void checkArgument(Argument argument) { return; } ArgumentValidationUtil validationUtil = new ArgumentValidationUtil(argument); - if (!validationUtil.isValidLiteralValue(argument.getValue(), fieldArgument.getType(), getValidationContext().getSchema())) { + if (!validationUtil.isValidLiteralValue(argument.getValue(), fieldArgument.getType(), getValidationContext().getSchema(), getValidationContext().getGraphQLContext(), Locale.getDefault())) { String message = i18n(WrongType, validationUtil.getMsgAndArgs()); addError(ValidationError.newValidationError() .validationErrorType(WrongType) diff --git a/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java b/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java index e00f1b87da..c67026e8b6 100644 --- a/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java +++ b/src/main/java/graphql/validation/rules/VariableDefaultValuesOfCorrectType.java @@ -7,6 +7,8 @@ import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; +import java.util.Locale; + import static graphql.schema.GraphQLTypeUtil.simplePrint; import static graphql.validation.ValidationErrorType.BadValueForDefaultArg; @@ -25,7 +27,7 @@ public void checkVariableDefinition(VariableDefinition variableDefinition) { return; } if (variableDefinition.getDefaultValue() != null - && !getValidationUtil().isValidLiteralValue(variableDefinition.getDefaultValue(), inputType, getValidationContext().getSchema())) { + && !getValidationUtil().isValidLiteralValue(variableDefinition.getDefaultValue(), inputType, getValidationContext().getSchema(), getValidationContext().getGraphQLContext(), Locale.getDefault())) { String message = i18n(BadValueForDefaultArg, "VariableDefaultValuesOfCorrectType.badDefault", variableDefinition.getDefaultValue(), simplePrint(inputType)); addError(BadValueForDefaultArg, variableDefinition.getSourceLocation(), message); } diff --git a/src/main/java/graphql/validation/rules/VariableTypesMatch.java b/src/main/java/graphql/validation/rules/VariableTypesMatch.java index 72ca603df2..de395850af 100644 --- a/src/main/java/graphql/validation/rules/VariableTypesMatch.java +++ b/src/main/java/graphql/validation/rules/VariableTypesMatch.java @@ -65,14 +65,14 @@ public void checkVariable(VariableReference variableReference) { if (schemaDefault.isPresent() && schemaDefault.get().isLiteral()) { schemaDefaultValue = (Value) schemaDefault.get().getValue(); } else if (schemaDefault.isPresent() && schemaDefault.get().isSet()) { - schemaDefaultValue = ValuesResolver.valueToLiteral(schemaDefault.get(), expectedType); + schemaDefaultValue = ValuesResolver.valueToLiteral(schemaDefault.get(), expectedType, getValidationContext().getGraphQLContext(), getValidationContext().getI18n().getLocale()); } if (expectedType == null) { // we must have a unknown variable say to not have a known type return; } if (!variablesTypesMatcher.doesVariableTypesMatch(variableType, variableDefinition.getDefaultValue(), expectedType) && - !variablesTypesMatcher.doesVariableTypesMatch(variableType, schemaDefaultValue, expectedType)) { + !variablesTypesMatcher.doesVariableTypesMatch(variableType, schemaDefaultValue, expectedType)) { GraphQLType effectiveType = variablesTypesMatcher.effectiveType(variableType, variableDefinition.getDefaultValue()); String message = i18n(VariableTypeMismatch, "VariableTypesMatchRule.unexpectedType", GraphQLTypeUtil.simplePrint(effectiveType), diff --git a/src/main/resources/i18n/Scalars.properties b/src/main/resources/i18n/Scalars.properties new file mode 100644 index 0000000000..d392ad798e --- /dev/null +++ b/src/main/resources/i18n/Scalars.properties @@ -0,0 +1,29 @@ +# +# This resource bundle is used for the scalar code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +Scalar.unexpectedAstType=Expected an AST type of ''{0}'' but it was a ''{1}''. +# +Enum.badInput=Invalid input for enum ''{0}''. Unknown value ''{1}''. +Enum.badName=Invalid input for enum ''{0}''. No value found for name ''{1}'' +Enum.unallowableValue=Literal value not in allowable values for enum ''{0}'' - ''{1}''. +# +Int.notInt=Expected a value that can be converted to type ''Int'' but it was a ''{0}'' +Int.outsideRange=Expected value to be in the integer range, but it was a ''{0}'' +# +ID.notId=Expected a value that can be converted to type ''ID'' but it was a ''{0}'' +ID.unexpectedAstType=Expected an AST type of ''IntValue'' or ''StringValue'' but it was a ''{0}''. +# +Float.notFloat=Expected a value that can be converted to type ''Float'' but it was a ''{0}'' +Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' but it was a ''{0}''. +# +Boolean.notBoolean=Expected a value that can be converted to type ''Boolean'' but it was a ''{0}'' +Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}''. diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 0ae487f569..af9f0d0595 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -369,7 +369,7 @@ class GraphQLTest extends Specification { then: result.errors.size() == 1 - result.errors[0].message == "Validation error (WrongType@[foo]) : argument 'bar' with value 'IntValue{value=12345678910}' is not a valid 'Int' - Expected value to be in the Integer range but it was '12345678910'" + result.errors[0].message == "Validation error (WrongType@[foo]) : argument 'bar' with value 'IntValue{value=12345678910}' is not a valid 'Int' - Expected value to be in the integer range, but it was a '12345678910'" } @SuppressWarnings("GroovyAssignabilityCheck") diff --git a/src/test/groovy/graphql/Issue2001.groovy b/src/test/groovy/graphql/Issue2001.groovy index 7c39e0132e..e18b69be5a 100644 --- a/src/test/groovy/graphql/Issue2001.groovy +++ b/src/test/groovy/graphql/Issue2001.groovy @@ -22,7 +22,7 @@ class Issue2001 extends Specification { def closure = { def argument = it.fieldDefinition.getDirective("test").getArgument("value") as GraphQLArgument - return ValuesResolver.valueToInternalValue(argument.getArgumentValue(), argument.getType())[0] + return ValuesResolver.valueToInternalValue(argument.getArgumentValue(), argument.getType(), GraphQLContext.getDefault(), Locale.getDefault())[0] } def graphql = TestUtil.graphQL(spec, RuntimeWiring.newRuntimeWiring() .type("Query", { diff --git a/src/test/groovy/graphql/Issue739.groovy b/src/test/groovy/graphql/Issue739.groovy index 0ceb24dd91..e3e579ab6b 100644 --- a/src/test/groovy/graphql/Issue739.groovy +++ b/src/test/groovy/graphql/Issue739.groovy @@ -106,7 +106,7 @@ class Issue739 extends Specification { varResult.data == null varResult.errors.size() == 1 varResult.errors[0].errorType == ErrorType.ValidationError - varResult.errors[0].message == "Variable 'input' has an invalid value: Expected type 'Int' but was 'String'." + varResult.errors[0].message == "Variable 'input' has an invalid value: Expected a value that can be converted to type 'Int' but it was a 'String'" varResult.errors[0].locations == [new SourceLocation(1, 11)] } } diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 29a6c4b771..2a082b20c8 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -2,6 +2,7 @@ package graphql.execution import graphql.ErrorType import graphql.ExecutionResult +import graphql.GraphQLContext import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters @@ -82,6 +83,8 @@ class AsyncExecutionStrategyTest extends Specification { .operationDefinition(operation) .instrumentation(SimpleInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) + .graphQLContext(GraphQLContext.getDefault()) + .locale(Locale.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -121,6 +124,8 @@ class AsyncExecutionStrategyTest extends Specification { .operationDefinition(operation) .valueUnboxer(ValueUnboxer.DEFAULT) .instrumentation(SimpleInstrumentation.INSTANCE) + .locale(Locale.getDefault()) + .graphQLContext(GraphQLContext.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -162,6 +167,8 @@ class AsyncExecutionStrategyTest extends Specification { .operationDefinition(operation) .valueUnboxer(ValueUnboxer.DEFAULT) .instrumentation(SimpleInstrumentation.INSTANCE) + .graphQLContext(GraphQLContext.getDefault()) + .locale(Locale.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -202,6 +209,8 @@ class AsyncExecutionStrategyTest extends Specification { .operationDefinition(operation) .instrumentation(SimpleInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) + .locale(Locale.getDefault()) + .graphQLContext(GraphQLContext.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -240,6 +249,8 @@ class AsyncExecutionStrategyTest extends Specification { .executionId(ExecutionId.generate()) .operationDefinition(operation) .valueUnboxer(ValueUnboxer.DEFAULT) + .graphQLContext(GraphQLContext.getDefault()) + .locale(Locale.getDefault()) .instrumentation(new SimpleInstrumentation() { @Override ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { @@ -251,13 +262,11 @@ class AsyncExecutionStrategyTest extends Specification { } @Override - public void onDispatched(CompletableFuture result) { - + void onDispatched(CompletableFuture result) { } @Override - public void onCompleted(ExecutionResult result, Throwable t) { - + void onCompleted(ExecutionResult result, Throwable t) { } } } diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index 7730f51b98..9cccbd5552 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -1,6 +1,6 @@ package graphql.execution - +import graphql.GraphQLContext import graphql.execution.instrumentation.SimpleInstrumentation import graphql.language.Field import graphql.language.OperationDefinition @@ -86,6 +86,8 @@ class AsyncSerialExecutionStrategyTest extends Specification { .operationDefinition(operation) .instrumentation(SimpleInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) + .locale(Locale.getDefault()) + .graphQLContext(GraphQLContext.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() @@ -130,6 +132,8 @@ class AsyncSerialExecutionStrategyTest extends Specification { .operationDefinition(operation) .instrumentation(SimpleInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) + .locale(Locale.getDefault()) + .graphQLContext(GraphQLContext.getDefault()) .build() ExecutionStrategyParameters executionStrategyParameters = ExecutionStrategyParameters .newParameters() diff --git a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy index 2a1a642c5b..354f57b5de 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy @@ -2,6 +2,7 @@ package graphql.execution import graphql.ErrorType import graphql.ExecutionInput +import graphql.GraphQLContext import graphql.GraphQLException import graphql.TestUtil import graphql.language.Argument @@ -37,13 +38,17 @@ import static graphql.schema.GraphQLNonNull.nonNull class ValuesResolverTest extends Specification { + def graphQLContext = GraphQLContext.getDefault() + def locale = Locale.getDefault() + + @Unroll def "getVariableValues: simple variable input #inputValue"() { given: def schema = TestUtil.schemaWithInputType(inputType) VariableDefinition variableDefinition = new VariableDefinition("variable", variableType, null) when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue]), graphQLContext, locale) then: resolvedValues.get('variable') == outputValue @@ -72,7 +77,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new TypeName("Person")) when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue]), graphQLContext, locale) then: resolvedValues.get('variable') == outputValue where: @@ -112,7 +117,7 @@ class ValuesResolverTest extends Specification { when: def obj = new Person('a', 123) - ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: obj])) + ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: obj]), graphQLContext, locale) then: thrown(CoercingParseValueException) } @@ -123,7 +128,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new ListType(new TypeName("String"))) String value = "world" when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value]), graphQLContext, locale) then: resolvedValues.get('variable') == ['world'] } @@ -134,7 +139,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new ListType(new TypeName("String"))) List value = ["hello","world"] when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value]), graphQLContext, locale) then: resolvedValues.get('variable') == ['hello','world'] } @@ -145,7 +150,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new ListType(new TypeName("String"))) String[] value = ["hello","world"] as String[] when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: value]), graphQLContext, locale) then: resolvedValues.get('variable') == ['hello','world'] } @@ -157,7 +162,7 @@ class ValuesResolverTest extends Specification { def argument = new Argument("arg", new VariableReference("var")) when: - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: values['arg'] == 'hello' @@ -174,7 +179,7 @@ class ValuesResolverTest extends Specification { when: def variables = CoercedVariables.emptyVariables() - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: values['arg'] == 'hello' @@ -204,7 +209,7 @@ class ValuesResolverTest extends Specification { when: def argument = new Argument("arg", inputValue) - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables()) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables(), graphQLContext, locale) then: values['arg'] == outputValue @@ -252,7 +257,7 @@ class ValuesResolverTest extends Specification { when: def argument = new Argument("arg", inputValue) - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables()) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables(), graphQLContext, locale) then: values['arg'] == outputValue @@ -300,7 +305,7 @@ class ValuesResolverTest extends Specification { def fieldArgument1 = newArgument().name("arg1").type(enumType).build() def fieldArgument2 = newArgument().name("arg2").type(enumType).build() when: - def values = ValuesResolver.getArgumentValues([fieldArgument1, fieldArgument2], [argument1, argument2], CoercedVariables.emptyVariables()) + def values = ValuesResolver.getArgumentValues([fieldArgument1, fieldArgument2], [argument1, argument2], CoercedVariables.emptyVariables(), graphQLContext, locale) then: values['arg1'] == 'PLUTO' @@ -317,7 +322,7 @@ class ValuesResolverTest extends Specification { def fieldArgument = newArgument().name("arg").type(list(GraphQLBoolean)).build() when: - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables()) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables(), graphQLContext, locale) then: values['arg'] == [true, false] @@ -331,7 +336,7 @@ class ValuesResolverTest extends Specification { def fieldArgument = newArgument().name("arg").type(list(GraphQLString)).build() when: - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables()) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], CoercedVariables.emptyVariables(), graphQLContext, locale) then: values['arg'] == ['world'] @@ -349,7 +354,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new TypeName("Test")) when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue]), graphQLContext, locale) then: resolvedValues.get('variable') == outputValue where: @@ -377,7 +382,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new TypeName("InputObject")) when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue])) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue]), graphQLContext, locale) then: resolvedValues.get('variable') == outputValue @@ -404,7 +409,7 @@ class ValuesResolverTest extends Specification { VariableDefinition variableDefinition = new VariableDefinition("variable", new TypeName("InputObject")) when: - ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue])) + ValuesResolver.coerceVariableValues(schema, [variableDefinition], RawVariables.of([variable: inputValue]), graphQLContext, locale) then: thrown(GraphQLException) @@ -423,7 +428,7 @@ class ValuesResolverTest extends Specification { VariableDefinition barVarDef = new VariableDefinition("bar", new TypeName("String")) when: - def resolvedValues = ValuesResolver.coerceVariableValues(schema, [fooVarDef, barVarDef], RawVariables.of(InputValue)) + def resolvedValues = ValuesResolver.coerceVariableValues(schema, [fooVarDef, barVarDef], RawVariables.of(InputValue), graphQLContext, locale) then: resolvedValues.toMap() == outputValue @@ -441,7 +446,7 @@ class ValuesResolverTest extends Specification { VariableDefinition fooVarDef = new VariableDefinition("foo", new NonNullType(new TypeName("String"))) when: - ValuesResolver.coerceVariableValues(schema, [fooVarDef], RawVariables.emptyVariables()) + ValuesResolver.coerceVariableValues(schema, [fooVarDef], RawVariables.emptyVariables(), graphQLContext, locale) then: thrown(GraphQLException) @@ -460,7 +465,7 @@ class ValuesResolverTest extends Specification { def variableValuesMap = RawVariables.of(["foo": null, "bar": "barValue"]) when: - def resolvedVars = ValuesResolver.coerceVariableValues(schema, [fooVarDef, barVarDef], variableValuesMap) + def resolvedVars = ValuesResolver.coerceVariableValues(schema, [fooVarDef, barVarDef], variableValuesMap, graphQLContext, locale) then: resolvedVars.get('foo') == null @@ -477,7 +482,7 @@ class ValuesResolverTest extends Specification { def variableValuesMap = RawVariables.of(["foo": null]) when: - ValuesResolver.coerceVariableValues(schema, [fooVarDef], variableValuesMap) + ValuesResolver.coerceVariableValues(schema, [fooVarDef], variableValuesMap, graphQLContext, locale) then: def error = thrown(NonNullableValueCoercedAsNullException) @@ -495,7 +500,7 @@ class ValuesResolverTest extends Specification { def variableValuesMap = RawVariables.of(["foo": [null]]) when: - ValuesResolver.coerceVariableValues(schema, [fooVarDef], variableValuesMap) + ValuesResolver.coerceVariableValues(schema, [fooVarDef], variableValuesMap, graphQLContext, locale) then: def error = thrown(NonNullableValueCoercedAsNullException) @@ -515,7 +520,7 @@ class ValuesResolverTest extends Specification { when: def variables = CoercedVariables.emptyVariables() - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: values['arg'] == null @@ -532,7 +537,7 @@ class ValuesResolverTest extends Specification { when: def variables = CoercedVariables.of(["var": null]) - def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables) + def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: values['arg'] == null @@ -549,7 +554,7 @@ class ValuesResolverTest extends Specification { when: def variables = CoercedVariables.of(["var": null]) - ValuesResolver.getArgumentValues([fieldArgument], [argument], variables) + ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: def error = thrown(NonNullableValueCoercedAsNullException) @@ -595,7 +600,7 @@ class ValuesResolverTest extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Invalid input for Enum \'PositionType\'. No value found for name \'UNKNOWN_POSITION\'' + executionResult.errors[0].message == "Variable 'input' has an invalid value: Invalid input for enum 'PositionType'. No value found for name 'UNKNOWN_POSITION'" executionResult.errors[0].locations == [new SourceLocation(2, 35)] } @@ -633,7 +638,7 @@ class ValuesResolverTest extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Expected type \'Boolean\' but was \'String\'.' + executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a value that can be converted to type 'Boolean' but it was a 'String'" executionResult.errors[0].locations == [new SourceLocation(2, 35)] } @@ -671,7 +676,7 @@ class ValuesResolverTest extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Expected type \'Float\' but was \'String\'.' + executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a value that can be converted to type 'Float' but it was a 'String'" executionResult.errors[0].locations == [new SourceLocation(2, 35)] } } \ No newline at end of file diff --git a/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy b/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy index e52d4b216c..ff883e0f78 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy @@ -1,6 +1,6 @@ package graphql.language - +import graphql.GraphQLContext import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLInputObjectType import spock.lang.Ignore @@ -11,27 +11,30 @@ import static graphql.Scalars.GraphQLFloat import static graphql.Scalars.GraphQLID import static graphql.Scalars.GraphQLInt import static graphql.Scalars.GraphQLString -import static graphql.execution.ValuesResolver.valueToLiteralLegacy +import static graphql.execution.ValuesResolverLegacy.valueToLiteralLegacy import static graphql.language.BooleanValue.newBooleanValue import static graphql.schema.GraphQLList.list import static graphql.schema.GraphQLNonNull.nonNull class ValuesResolverTestLegacy extends Specification { + def graphQLContext = GraphQLContext.getDefault() + def locale = Locale.getDefault() + def 'converts boolean values to ASTs'() { expect: - valueToLiteralLegacy(true, GraphQLBoolean).isEqualTo(newBooleanValue(true).build()) + valueToLiteralLegacy(true, GraphQLBoolean, graphQLContext, locale).isEqualTo(newBooleanValue(true).build()) - valueToLiteralLegacy(false, GraphQLBoolean).isEqualTo(newBooleanValue(false).build()) + valueToLiteralLegacy(false, GraphQLBoolean, graphQLContext, locale).isEqualTo(newBooleanValue(false).build()) - valueToLiteralLegacy(null, GraphQLBoolean) == null + valueToLiteralLegacy(null, GraphQLBoolean, graphQLContext, locale) == null - valueToLiteralLegacy(0, GraphQLBoolean).isEqualTo(newBooleanValue(false).build()) + valueToLiteralLegacy(0, GraphQLBoolean, graphQLContext, locale).isEqualTo(newBooleanValue(false).build()) - valueToLiteralLegacy(1, GraphQLBoolean).isEqualTo(newBooleanValue(true).build()) + valueToLiteralLegacy(1, GraphQLBoolean, graphQLContext, locale).isEqualTo(newBooleanValue(true).build()) def NonNullBoolean = nonNull(GraphQLBoolean) - valueToLiteralLegacy(0, NonNullBoolean).isEqualTo(newBooleanValue(false).build()) + valueToLiteralLegacy(0, NonNullBoolean, graphQLContext, locale).isEqualTo(newBooleanValue(false).build()) } BigInteger bigInt(int i) { @@ -40,60 +43,60 @@ class ValuesResolverTestLegacy extends Specification { def 'converts Int values to Int ASTs'() { expect: - valueToLiteralLegacy(123.0, GraphQLInt).isEqualTo(IntValue.newIntValue(bigInt(123)).build()) + valueToLiteralLegacy(123.0, GraphQLInt, graphQLContext, locale).isEqualTo(IntValue.newIntValue(bigInt(123)).build()) - valueToLiteralLegacy(1e4, GraphQLInt).isEqualTo(IntValue.newIntValue(bigInt(10000)).build()) + valueToLiteralLegacy(1e4, GraphQLInt, graphQLContext, locale).isEqualTo(IntValue.newIntValue(bigInt(10000)).build()) } def 'converts Float values to Int/Float ASTs'() { expect: - valueToLiteralLegacy(123.0, GraphQLFloat).isEqualTo(FloatValue.newFloatValue(123.0).build()) + valueToLiteralLegacy(123.0, GraphQLFloat, graphQLContext, locale).isEqualTo(FloatValue.newFloatValue(123.0).build()) - valueToLiteralLegacy(123.5, GraphQLFloat).isEqualTo(FloatValue.newFloatValue(123.5).build()) + valueToLiteralLegacy(123.5, GraphQLFloat, graphQLContext, locale).isEqualTo(FloatValue.newFloatValue(123.5).build()) - valueToLiteralLegacy(1e4, GraphQLFloat).isEqualTo(FloatValue.newFloatValue(10000.0).build()) + valueToLiteralLegacy(1e4, GraphQLFloat, graphQLContext, locale).isEqualTo(FloatValue.newFloatValue(10000.0).build()) - valueToLiteralLegacy(1e40, GraphQLFloat).isEqualTo(FloatValue.newFloatValue(1.0e40).build()) + valueToLiteralLegacy(1e40, GraphQLFloat, graphQLContext, locale).isEqualTo(FloatValue.newFloatValue(1.0e40).build()) } def 'converts String values to String ASTs'() { expect: - valueToLiteralLegacy('hello', GraphQLString).isEqualTo(new StringValue('hello')) + valueToLiteralLegacy('hello', GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('hello')) - valueToLiteralLegacy('VALUE', GraphQLString).isEqualTo(new StringValue('VALUE')) + valueToLiteralLegacy('VALUE', GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('VALUE')) - valueToLiteralLegacy('VA\n\t\f\r\b\\LUE', GraphQLString).isEqualTo(new StringValue('VA\n\t\f\r\b\\LUE')) + valueToLiteralLegacy('VA\n\t\f\r\b\\LUE', GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('VA\n\t\f\r\b\\LUE')) - valueToLiteralLegacy('VA\\L\"UE', GraphQLString).isEqualTo(new StringValue('VA\\L\"UE')) + valueToLiteralLegacy('VA\\L\"UE', GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('VA\\L\"UE')) - valueToLiteralLegacy(123, GraphQLString).isEqualTo(new StringValue('123')) + valueToLiteralLegacy(123, GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('123')) - valueToLiteralLegacy(false, GraphQLString).isEqualTo(new StringValue('false')) + valueToLiteralLegacy(false, GraphQLString, graphQLContext, locale).isEqualTo(new StringValue('false')) - valueToLiteralLegacy(null, GraphQLString) == null + valueToLiteralLegacy(null, GraphQLString, graphQLContext, locale) == null } def 'converts ID values to Int/String ASTs'() { expect: - valueToLiteralLegacy('hello', GraphQLID).isEqualTo(new StringValue('hello')) + valueToLiteralLegacy('hello', GraphQLID, graphQLContext, locale).isEqualTo(new StringValue('hello')) - valueToLiteralLegacy('VALUE', GraphQLID).isEqualTo(new StringValue('VALUE')) + valueToLiteralLegacy('VALUE', GraphQLID, graphQLContext, locale).isEqualTo(new StringValue('VALUE')) // Note: EnumValues cannot contain non-identifier characters - valueToLiteralLegacy('VA\nLUE', GraphQLID).isEqualTo(new StringValue('VA\nLUE')) + valueToLiteralLegacy('VA\nLUE', GraphQLID, graphQLContext, locale).isEqualTo(new StringValue('VA\nLUE')) // Note: IntValues are used when possible. - valueToLiteralLegacy(123, GraphQLID).isEqualTo(new IntValue(bigInt(123))) + valueToLiteralLegacy(123, GraphQLID, graphQLContext, locale).isEqualTo(new IntValue(bigInt(123))) - valueToLiteralLegacy(null, GraphQLID) == null + valueToLiteralLegacy(null, GraphQLID, graphQLContext, locale) == null } def 'does not converts NonNull values to NullValue'() { expect: def NonNullBoolean = nonNull(GraphQLBoolean) - valueToLiteralLegacy(null, NonNullBoolean) == null + valueToLiteralLegacy(null, NonNullBoolean, graphQLContext, locale) == null } def complexValue = { someArbitrary: 'complexValue' } @@ -107,33 +110,33 @@ class ValuesResolverTestLegacy extends Specification { def 'converts string values to Enum ASTs if possible'() { expect: - valueToLiteralLegacy('HELLO', myEnum).isEqualTo(new EnumValue('HELLO')) + valueToLiteralLegacy('HELLO', myEnum, graphQLContext, locale).isEqualTo(new EnumValue('HELLO')) - valueToLiteralLegacy(complexValue, myEnum).isEqualTo(new EnumValue('COMPLEX')) + valueToLiteralLegacy(complexValue, myEnum, graphQLContext, locale).isEqualTo(new EnumValue('COMPLEX')) } def 'converts array values to List ASTs'() { expect: - valueToLiteralLegacy(['FOO', 'BAR'], list(GraphQLString)).isEqualTo( + valueToLiteralLegacy(['FOO', 'BAR'], list(GraphQLString), graphQLContext, locale).isEqualTo( new ArrayValue([new StringValue('FOO'), new StringValue('BAR')]) ) - valueToLiteralLegacy(['HELLO', 'GOODBYE'], list(myEnum)).isEqualTo( + valueToLiteralLegacy(['HELLO', 'GOODBYE'], list(myEnum), graphQLContext, locale).isEqualTo( new ArrayValue([new EnumValue('HELLO'), new EnumValue('GOODBYE')]) ) } def 'converts list singletons'() { expect: - valueToLiteralLegacy('FOO', list(GraphQLString)).isEqualTo( + valueToLiteralLegacy('FOO', list(GraphQLString), graphQLContext, locale).isEqualTo( new StringValue('FOO') ) } def 'converts list to lists'() { expect: - valueToLiteralLegacy(['hello', 'world'], list(GraphQLString)).isEqualTo( + valueToLiteralLegacy(['hello', 'world'], list(GraphQLString), graphQLContext, locale).isEqualTo( new ArrayValue(['hello', 'world']) ) } @@ -141,7 +144,7 @@ class ValuesResolverTestLegacy extends Specification { def 'converts arrays to lists'() { String[] sArr = ['hello', 'world'] as String[] expect: - valueToLiteralLegacy(sArr, list(GraphQLString)).isEqualTo( + valueToLiteralLegacy(sArr, list(GraphQLString), graphQLContext, locale).isEqualTo( new ArrayValue(['hello', 'world']) ) } @@ -165,19 +168,19 @@ class ValuesResolverTestLegacy extends Specification { .build() expect: - valueToLiteralLegacy([foo: 3, bar: 'HELLO'], inputObj).isEqualTo( + valueToLiteralLegacy([foo: 3, bar: 'HELLO'], inputObj, graphQLContext, locale).isEqualTo( new ObjectValue([new ObjectField("foo", new IntValue(bigInt(3))), new ObjectField("bar", new EnumValue('HELLO')), ]) ) - valueToLiteralLegacy(new SomePojo(), inputObj).isEqualTo( + valueToLiteralLegacy(new SomePojo(), inputObj, graphQLContext, locale).isEqualTo( new ObjectValue([new ObjectField("foo", new IntValue(bigInt(3))), new ObjectField("bar", new EnumValue('HELLO')), ]) ) - valueToLiteralLegacy(new SomePojoWithFields(), inputObj).isEqualTo( + valueToLiteralLegacy(new SomePojoWithFields(), inputObj, graphQLContext, locale).isEqualTo( new ObjectValue([new ObjectField("foo", new IntValue(bigInt(3))), new ObjectField("bar", new EnumValue('HELLO')), ]) @@ -195,7 +198,7 @@ class ValuesResolverTestLegacy extends Specification { .field({ f -> f.name("bar").type(myEnum) }) .build() - valueToLiteralLegacy([foo: null], inputObj).isEqualTo( + valueToLiteralLegacy([foo: null], inputObj, graphQLContext, locale).isEqualTo( new ObjectValue([new ObjectField("foo", null)]) ) } diff --git a/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy b/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy index a728a99d6a..ec406d7c42 100644 --- a/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy +++ b/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy @@ -1,5 +1,6 @@ package graphql.execution.directives +import graphql.GraphQLContext import graphql.TestUtil import graphql.execution.MergedField import spock.lang.Specification @@ -30,7 +31,7 @@ class QueryDirectivesImplTest extends Specification { def mergedField = MergedField.newMergedField([f1, f2]).build() - def impl = new QueryDirectivesImpl(mergedField, schema, [var: 10]) + def impl = new QueryDirectivesImpl(mergedField, schema, [var: 10], GraphQLContext.getDefault(), Locale.getDefault()) when: def directives = impl.getImmediateDirectivesByName() diff --git a/src/test/groovy/graphql/parser/ParserTest.groovy b/src/test/groovy/graphql/parser/ParserTest.groovy index 6a76ace1b4..539e7b0c2d 100644 --- a/src/test/groovy/graphql/parser/ParserTest.groovy +++ b/src/test/groovy/graphql/parser/ParserTest.groovy @@ -858,6 +858,24 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" operationDefinition.getComments()[0].content == " Represents the 😕 emoji." } + + def "the parser can be invoked via parser environment"() { + def input = ''' + # Represents the 😕 emoji. + { + foo + } + ''' + when: + def parserEnvironment = ParserEnvironment.newParserEnvironment().document(input).build() + + Document document = Parser.parse(parserEnvironment) + OperationDefinition operationDefinition = (document.definitions[0] as OperationDefinition) + + then: + operationDefinition.getComments()[0].content == " Represents the 😕 emoji." + } + def "can override antlr to ast"() { def query = ''' diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy index 9787fd3d14..e27d618db5 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy @@ -2,6 +2,7 @@ package graphql.schema.idl import graphql.ExecutionInput import graphql.GraphQL +import graphql.GraphQLContext import graphql.execution.ValuesResolver import graphql.schema.Coercing import graphql.schema.CoercingParseLiteralException @@ -143,7 +144,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { GraphQLDirective directive = environment.getDirective() def arg = directive.getArgument("target") - String target = ValuesResolver.valueToInternalValue(arg.getArgumentValue(), arg.getType()) + String target = ValuesResolver.valueToInternalValue(arg.getArgumentValue(), arg.getType(), GraphQLContext.getDefault(), Locale.getDefault()) assert name == target, " The target $target is not equal to the object name $name" return element } diff --git a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy index 8ed1d8e55c..0a34fb4d16 100644 --- a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy +++ b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy @@ -1,5 +1,6 @@ package graphql.validation +import graphql.GraphQLContext import graphql.StarWarsSchema import graphql.language.ArrayValue import graphql.language.BooleanValue @@ -27,6 +28,8 @@ class ValidationUtilTest extends Specification { def schema = GraphQLSchema.newSchema().query(StarWarsSchema.queryType).build() def validationUtil = new ValidationUtil() + def graphQLContext = GraphQLContext.getDefault() + def locale = Locale.getDefault() def "getUnmodified type of list of nonNull"() { given: @@ -51,32 +54,32 @@ class ValidationUtilTest extends Specification { def "null and NonNull is invalid"() { expect: - !validationUtil.isValidLiteralValue(null, nonNull(GraphQLString),schema) + !validationUtil.isValidLiteralValue(null, nonNull(GraphQLString), schema, graphQLContext, locale) } def "NullValue and NonNull is invalid"() { expect: - !validationUtil.isValidLiteralValue(NullValue.newNullValue().build(), nonNull(GraphQLString),schema) + !validationUtil.isValidLiteralValue(NullValue.newNullValue().build(), nonNull(GraphQLString), schema, graphQLContext, locale) } def "a nonNull value for a NonNull type is valid"() { expect: - validationUtil.isValidLiteralValue(new StringValue("string"), nonNull(GraphQLString),schema) + validationUtil.isValidLiteralValue(new StringValue("string"), nonNull(GraphQLString), schema, graphQLContext, locale) } def "null is valid when type is NonNull"() { expect: - validationUtil.isValidLiteralValue(null, GraphQLString,schema) + validationUtil.isValidLiteralValue(null, GraphQLString, schema, graphQLContext, locale) } def "NullValue is valid when type is NonNull"() { expect: - validationUtil.isValidLiteralValue(NullValue.newNullValue().build(), GraphQLString,schema) + validationUtil.isValidLiteralValue(NullValue.newNullValue().build(), GraphQLString, schema, graphQLContext, locale) } def "variables are valid"() { expect: - validationUtil.isValidLiteralValue(new VariableReference("var"), GraphQLBoolean,schema) + validationUtil.isValidLiteralValue(new VariableReference("var"), GraphQLBoolean, schema, graphQLContext, locale) } def "ArrayValue and ListType is invalid when one entry is invalid"() { @@ -85,7 +88,7 @@ class ValidationUtilTest extends Specification { def type = list(GraphQLString) expect: - !validationUtil.isValidLiteralValue(arrayValue, type,schema) + !validationUtil.isValidLiteralValue(arrayValue, type,schema,graphQLContext, locale) } def "One value is a single element List"() { @@ -93,7 +96,7 @@ class ValidationUtilTest extends Specification { def singleValue = new BooleanValue(true) def type = list(GraphQLBoolean) expect: - validationUtil.isValidLiteralValue(singleValue, type,schema) + validationUtil.isValidLiteralValue(singleValue, type,schema,graphQLContext,locale) } def "a valid array"() { @@ -102,19 +105,19 @@ class ValidationUtilTest extends Specification { def type = list(GraphQLString) expect: - validationUtil.isValidLiteralValue(arrayValue, type,schema) + validationUtil.isValidLiteralValue(arrayValue, type,schema, graphQLContext,locale) } def "a valid scalar"() { given: expect: - validationUtil.isValidLiteralValue(new BooleanValue(true), GraphQLBoolean,schema) + validationUtil.isValidLiteralValue(new BooleanValue(true), GraphQLBoolean, schema, graphQLContext, locale) } def "invalid scalar"() { given: expect: - !validationUtil.isValidLiteralValue(new BooleanValue(true), GraphQLString,schema) + !validationUtil.isValidLiteralValue(new BooleanValue(true), GraphQLString, schema, graphQLContext, locale) } def "valid enum"() { @@ -122,21 +125,21 @@ class ValidationUtilTest extends Specification { def enumType = GraphQLEnumType.newEnum().name("enumType").value("PLUTO").build() expect: - validationUtil.isValidLiteralValue(new EnumValue("PLUTO"), enumType,schema) + validationUtil.isValidLiteralValue(new EnumValue("PLUTO"), enumType, schema, graphQLContext, locale) } def "invalid enum value"() { given: def enumType = GraphQLEnumType.newEnum().name("enumType").value("PLUTO").build() expect: - !validationUtil.isValidLiteralValue(new StringValue("MARS"), enumType,schema) + !validationUtil.isValidLiteralValue(new StringValue("MARS"), enumType, schema, graphQLContext, locale) } def "invalid enum name"() { given: def enumType = GraphQLEnumType.newEnum().name("enumType").value("PLUTO").build() expect: - !validationUtil.isValidLiteralValue(new EnumValue("MARS"), enumType,schema) + !validationUtil.isValidLiteralValue(new EnumValue("MARS"), enumType, schema, graphQLContext, locale) } def "a valid ObjectValue"() { @@ -151,7 +154,7 @@ class ValidationUtilTest extends Specification { objectValue.objectField(new ObjectField("hello", new StringValue("world"))) expect: - validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema) + validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema, graphQLContext, locale) } def "a invalid ObjectValue with a invalid field"() { @@ -166,7 +169,7 @@ class ValidationUtilTest extends Specification { objectValue.objectField(new ObjectField("hello", new BooleanValue(false))) expect: - !validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema) + !validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema, graphQLContext,locale) } def "a invalid ObjectValue with a missing field"() { @@ -180,7 +183,7 @@ class ValidationUtilTest extends Specification { def objectValue = ObjectValue.newObjectValue().build() expect: - !validationUtil.isValidLiteralValue(objectValue, inputObjectType,schema) + !validationUtil.isValidLiteralValue(objectValue, inputObjectType,schema, graphQLContext,locale) } def "a valid ObjectValue with a nonNull field and default value"() { @@ -195,6 +198,6 @@ class ValidationUtilTest extends Specification { def objectValue = ObjectValue.newObjectValue() expect: - validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema) + validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema, graphQLContext,locale) } } diff --git a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy index b2b99db2bd..513e2884a5 100644 --- a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy @@ -1,5 +1,6 @@ package graphql.validation.rules +import graphql.GraphQLContext import graphql.language.Argument import graphql.language.ArrayValue import graphql.language.BooleanValue @@ -35,6 +36,8 @@ class ArgumentsOfCorrectTypeTest extends Specification { def setup() { argumentsOfCorrectType = new ArgumentsOfCorrectType(validationContext, errorCollector) + def context = GraphQLContext.getDefault() + validationContext.getGraphQLContext() >> context } def "valid type results in no error"() { @@ -77,7 +80,7 @@ class ArgumentsOfCorrectTypeTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType - validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'arg1' with value 'IntValue{value=1}' is not a valid 'String' - Expected AST type 'StringValue' but was 'IntValue'." + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'arg1' with value 'IntValue{value=1}' is not a valid 'String' - Expected an AST type of 'StringValue' but it was a 'IntValue'." } def "invalid input object type results in error"() { @@ -329,7 +332,7 @@ class ArgumentsOfCorrectTypeTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType - validationErrors.get(0).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'EnumValue{name='PRETTY'}' is not a valid 'DogCommand' - Expected enum literal value not in allowable values - 'EnumValue{name='PRETTY'}'." + validationErrors.get(0).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'EnumValue{name='PRETTY'}' is not a valid 'DogCommand' - Literal value not in allowable values for enum 'DogCommand' - 'EnumValue{name='PRETTY'}'." } static List validate(String query) { diff --git a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy index cbf1a3a518..e69baff7b2 100644 --- a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy @@ -1,6 +1,8 @@ package graphql.validation.rules +import graphql.GraphQLContext import graphql.TestUtil +import graphql.i18n.I18n import graphql.language.BooleanValue import graphql.language.TypeName import graphql.language.VariableDefinition @@ -18,6 +20,11 @@ class VariableDefaultValuesOfCorrectTypeTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() VariableDefaultValuesOfCorrectType defaultValuesOfCorrectType = new VariableDefaultValuesOfCorrectType(validationContext, errorCollector) + void setup() { + def context = GraphQLContext.getDefault() + validationContext.getGraphQLContext() >> context + } + def "default value has wrong type"() { given: BooleanValue defaultValue = BooleanValue.newBooleanValue(false).build() From 7e6bdd3b7bd47df71614bbac232367f15aec6e2c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 19:47:29 +1000 Subject: [PATCH 101/294] Move DataFetchingEnvironment test to home file --- .../execution/ExecutionStrategyTest.groovy | 9 ++++----- .../DataFetchingEnvironmentImplTest.groovy | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index 2b9009b01d..ddee907278 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -21,8 +21,6 @@ import graphql.parser.Parser import graphql.schema.Coercing import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment -import graphql.schema.FieldCoordinates -import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLScalarType @@ -504,13 +502,14 @@ class ExecutionStrategyTest extends Specification { NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) Argument argument = new Argument("arg1", new StringValue("argVal")) Field field = new Field("someField", [argument]) + MergedField mergedField = mergedField(field) ResultPath resultPath = ResultPath.rootPath().segment("test") def parameters = newParameters() .executionStepInfo(typeInfo) .source("source") .fields(mergedSelectionSet(["someField": [field]])) - .field(mergedField(field)) + .field(mergedField) .nonNullFieldValidator(nullableFieldValidator) .path(resultPath) .build() @@ -525,7 +524,7 @@ class ExecutionStrategyTest extends Specification { environment.graphQLSchema == schema environment.graphQlContext.get("key") == "context" environment.source == "source" - environment.fields == [field] // Retain deprecated for test coverage + environment.mergedField == mergedField environment.root == "root" environment.parentType == objectType environment.arguments == ["arg1": "argVal"] @@ -745,7 +744,7 @@ class ExecutionStrategyTest extends Specification { def "#842 completes value for java.util.Stream"() { given: ExecutionContext executionContext = buildContext() - Stream result = Stream.of(1, 2, 3) + Stream result = Stream.of(1L, 2L, 3L) def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index 724cd65516..22c4e7aeb8 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -4,8 +4,11 @@ import graphql.GraphQLContext import graphql.execution.CoercedVariables import graphql.execution.ExecutionId import graphql.execution.ExecutionStepInfo +import graphql.language.Argument +import graphql.language.Field import graphql.language.FragmentDefinition import graphql.language.OperationDefinition +import graphql.language.StringValue import graphql.language.TypeName import org.dataloader.BatchLoader import org.dataloader.DataLoader @@ -15,6 +18,7 @@ import spock.lang.Specification import java.util.concurrent.CompletableFuture import static graphql.StarWarsSchema.starWarsSchema +import static graphql.TestUtil.mergedField import static graphql.TestUtil.toDocument import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder import static graphql.schema.DataFetchingEnvironmentImpl.newDataFetchingEnvironment @@ -133,4 +137,18 @@ class DataFetchingEnvironmentImplTest extends Specification { dfe.getArgument("x") == "y" dfe.getArgumentOrDefault("x", "default") == "y" } + + def "deprecated getFields() method works"() { + when: + Argument argument = new Argument("arg1", new StringValue("argVal")) + Field field = new Field("someField", [argument]) + + def environment = newDataFetchingEnvironment(executionContext) + .mergedField(mergedField(field)) + .build() + + then: + environment.fields == [field] // Retain deprecated method for test coverage + } + } From 8c28102e318a7d3c2e9774c6b0df8b94f36803e2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 22 Aug 2022 20:11:01 +1000 Subject: [PATCH 102/294] Deprecate the context builder in ExecutionContextBuilder --- .../java/graphql/execution/ExecutionContextBuilder.java | 5 +++++ src/main/java/graphql/util/FpKit.java | 1 + .../graphql/execution/ExecutionContextBuilderTest.groovy | 6 +----- .../groovy/graphql/execution/ExecutionStrategyTest.groovy | 1 - .../graphql/schema/DataFetchingEnvironmentImplTest.groovy | 3 --- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/execution/ExecutionContextBuilder.java b/src/main/java/graphql/execution/ExecutionContextBuilder.java index 898c3786eb..12198cf879 100644 --- a/src/main/java/graphql/execution/ExecutionContextBuilder.java +++ b/src/main/java/graphql/execution/ExecutionContextBuilder.java @@ -131,6 +131,11 @@ public ExecutionContextBuilder subscriptionStrategy(ExecutionStrategy subscripti return this; } + /* + * @deprecated use {@link #graphQLContext(GraphQLContext)} instead + */ + @Deprecated + @DeprecatedAt("2021-07-05") public ExecutionContextBuilder context(Object context) { this.context = context; return this; diff --git a/src/main/java/graphql/util/FpKit.java b/src/main/java/graphql/util/FpKit.java index ce164b6595..21b6b95564 100644 --- a/src/main/java/graphql/util/FpKit.java +++ b/src/main/java/graphql/util/FpKit.java @@ -333,6 +333,7 @@ public static Supplier interThreadMemoize(Supplier delegate) { /** * Faster set intersection. * + * @param for two * @param set1 first set * @param set2 second set * @return intersection set diff --git a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy index fcffa7a866..eb35f277b7 100644 --- a/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionContextBuilderTest.groovy @@ -35,7 +35,7 @@ class ExecutionContextBuilderTest extends Specification { .subscriptionStrategy(subscriptionStrategy) .graphQLSchema(schema) .executionId(executionId) - .context(context) + .context(context) // Retain deprecated builder for test coverage .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) @@ -72,7 +72,6 @@ class ExecutionContextBuilderTest extends Specification { .subscriptionStrategy(subscriptionStrategy) .graphQLSchema(schema) .executionId(executionId) - .context(context) .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) @@ -108,7 +107,6 @@ class ExecutionContextBuilderTest extends Specification { .subscriptionStrategy(subscriptionStrategy) .graphQLSchema(schema) .executionId(executionId) - .context(context) .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) @@ -142,7 +140,6 @@ class ExecutionContextBuilderTest extends Specification { .subscriptionStrategy(subscriptionStrategy) .graphQLSchema(schema) .executionId(executionId) - .context(context) .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) @@ -181,7 +178,6 @@ class ExecutionContextBuilderTest extends Specification { .subscriptionStrategy(subscriptionStrategy) .graphQLSchema(schema) .executionId(executionId) - .context(context) .graphQLContext(graphQLContext) .root(root) .operationDefinition(operation) diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index ddee907278..adc5cd57a9 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -72,7 +72,6 @@ class ExecutionStrategyTest extends Specification { .mutationStrategy(executionStrategy) .subscriptionStrategy(executionStrategy) .coercedVariables(CoercedVariables.of(variables)) - .context("context") .graphQLContext(GraphQLContext.newContext().of("key","context").build()) .root("root") .dataLoaderRegistry(new DataLoaderRegistry()) diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index 22c4e7aeb8..f23475e90d 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -37,7 +37,6 @@ class DataFetchingEnvironmentImplTest extends Specification { def executionContext = newExecutionContextBuilder() .root("root") - .context("context") .graphQLContext(GraphQLContext.of(["key":"context"])) .executionId(executionId) .operationDefinition(operationDefinition) @@ -48,7 +47,6 @@ class DataFetchingEnvironmentImplTest extends Specification { .dataLoaderRegistry(dataLoaderRegistry) .build() - def "immutable arguments"() { def dataFetchingEnvironment = newDataFetchingEnvironment(executionContext).arguments([arg: "argVal"]) .build() @@ -64,7 +62,6 @@ class DataFetchingEnvironmentImplTest extends Specification { } def "copying works as expected from execution context"() { - when: def dfe = newDataFetchingEnvironment(executionContext) .build() From b1239701dc6ade48b4034d28829f8b3e5a2baa11 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 23 Aug 2022 07:08:06 +1000 Subject: [PATCH 103/294] Fix test after update from master --- src/main/java/graphql/schema/Coercing.java | 1 + .../groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/Coercing.java b/src/main/java/graphql/schema/Coercing.java index 669242d2f9..2f66ae83ca 100644 --- a/src/main/java/graphql/schema/Coercing.java +++ b/src/main/java/graphql/schema/Coercing.java @@ -203,6 +203,7 @@ default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLConte * * @param input an external input value * @param graphQLContext the graphql context in place + * @param locale the locale to use * * @return The literal matching the external input value. */ diff --git a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy index 30e5c7f9c9..7aa58093ec 100644 --- a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy @@ -1,5 +1,6 @@ package graphql.schema +import graphql.GraphQLContext import graphql.StarWarsSchema import graphql.language.ObjectValue import graphql.validation.ValidationUtil @@ -60,6 +61,7 @@ class GraphQLInputObjectTypeTest extends Specification { def "deprecated default value builder works"() { given: + def graphQLContext = GraphQLContext.getDefault() def schema = GraphQLSchema.newSchema().query(StarWarsSchema.queryType).build() def validationUtil = new ValidationUtil() def inputObjectType = GraphQLInputObjectType.newInputObject() @@ -72,6 +74,6 @@ class GraphQLInputObjectTypeTest extends Specification { def objectValue = ObjectValue.newObjectValue() expect: - validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema) + validationUtil.isValidLiteralValue(objectValue.build(), inputObjectType, schema, graphQLContext, Locale.ENGLISH) } } From f5b123fb35952ebdb9375ce1d0624f3b49e91a6c Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Tue, 23 Aug 2022 09:26:02 +1000 Subject: [PATCH 104/294] Removes the deprecated execute methods from GraphQL (#2932) --- src/main/java/graphql/GraphQL.java | 91 ------------------- src/test/groovy/graphql/MutationTest.groovy | 6 +- .../groovy/graphql/StarWarsQueryTest.groovy | 6 +- src/test/groovy/graphql/UnionTest.groovy | 6 +- ...tableNormalizedOperationFactoryTest.groovy | 4 +- 5 files changed, 15 insertions(+), 98 deletions(-) diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 4026ffc3c2..1210ee7115 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -324,97 +324,6 @@ public ExecutionResult execute(String query) { return execute(executionInput); } - /** - * Info: This sets context = root to be backwards compatible. - * - * @param query the query/mutation/subscription - * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * - * @return an {@link ExecutionResult} which can include errors - * - * @deprecated Use {@link #execute(ExecutionInput)} - */ - @Deprecated - @DeprecatedAt("2017-06-02") - public ExecutionResult execute(String query, Object context) { - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(query) - .context(context) - .root(context) // This we are doing do be backwards compatible - .build(); - return execute(executionInput); - } - - /** - * Info: This sets context = root to be backwards compatible. - * - * @param query the query/mutation/subscription - * @param operationName the name of the operation to execute - * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * - * @return an {@link ExecutionResult} which can include errors - * - * @deprecated Use {@link #execute(ExecutionInput)} - */ - @Deprecated - @DeprecatedAt("2017-06-02") - public ExecutionResult execute(String query, String operationName, Object context) { - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(query) - .operationName(operationName) - .context(context) - .root(context) // This we are doing do be backwards compatible - .build(); - return execute(executionInput); - } - - /** - * Info: This sets context = root to be backwards compatible. - * - * @param query the query/mutation/subscription - * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * @param variables variable values uses as argument - * - * @return an {@link ExecutionResult} which can include errors - * - * @deprecated Use {@link #execute(ExecutionInput)} - */ - @Deprecated - @DeprecatedAt("2017-06-02") - public ExecutionResult execute(String query, Object context, Map variables) { - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(query) - .context(context) - .root(context) // This we are doing do be backwards compatible - .variables(variables) - .build(); - return execute(executionInput); - } - - /** - * Info: This sets context = root to be backwards compatible. - * - * @param query the query/mutation/subscription - * @param operationName name of the operation to execute - * @param context custom object provided to each {@link graphql.schema.DataFetcher} - * @param variables variable values uses as argument - * - * @return an {@link ExecutionResult} which can include errors - * - * @deprecated Use {@link #execute(ExecutionInput)} - */ - @Deprecated - @DeprecatedAt("2017-06-02") - public ExecutionResult execute(String query, String operationName, Object context, Map variables) { - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(query) - .operationName(operationName) - .context(context) - .root(context) // This we are doing do be backwards compatible - .variables(variables) - .build(); - return execute(executionInput); - } /** * Executes the graphql query using the provided input object builder diff --git a/src/test/groovy/graphql/MutationTest.groovy b/src/test/groovy/graphql/MutationTest.groovy index 68da613740..d6613a2176 100644 --- a/src/test/groovy/graphql/MutationTest.groovy +++ b/src/test/groovy/graphql/MutationTest.groovy @@ -47,7 +47,8 @@ class MutationTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(query, new MutationSchema.SubscriptionRoot(6)) + def ei = ExecutionInput.newExecutionInput(query).root(new MutationSchema.SubscriptionRoot(6)).build() + def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(ei) then: @@ -93,7 +94,8 @@ class MutationTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(query, new MutationSchema.SubscriptionRoot(6)) + def ei = ExecutionInput.newExecutionInput(query).root(new MutationSchema.SubscriptionRoot(6)).build() + def executionResult = GraphQL.newGraphQL(MutationSchema.schema).build().execute(ei) then: diff --git a/src/test/groovy/graphql/StarWarsQueryTest.groovy b/src/test/groovy/graphql/StarWarsQueryTest.groovy index f28cb3fe1b..0ed8b46bb2 100644 --- a/src/test/groovy/graphql/StarWarsQueryTest.groovy +++ b/src/test/groovy/graphql/StarWarsQueryTest.groovy @@ -190,7 +190,8 @@ class StarWarsQueryTest extends Specification { ] ] when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data + def ei = ExecutionInput.newExecutionInput(query).variables(params).build() + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(ei).data then: result == expected @@ -212,7 +213,8 @@ class StarWarsQueryTest extends Specification { human: null ] when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data + def ei = ExecutionInput.newExecutionInput(query).variables(params).build() + def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(ei).data then: result == expected diff --git a/src/test/groovy/graphql/UnionTest.groovy b/src/test/groovy/graphql/UnionTest.groovy index 4fb9f2af25..5f8334130d 100644 --- a/src/test/groovy/graphql/UnionTest.groovy +++ b/src/test/groovy/graphql/UnionTest.groovy @@ -96,7 +96,8 @@ class UnionTest extends Specification { ] when: - def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(query, GarfieldSchema.john) + def ei = ExecutionInput.newExecutionInput(query).root(GarfieldSchema.john).build() + def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(ei) then: executionResult.data == expectedResult @@ -148,7 +149,8 @@ class UnionTest extends Specification { ] ] when: - def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(query, GarfieldSchema.john) + def ei = ExecutionInput.newExecutionInput(query).root(GarfieldSchema.john).build() + def executionResult = GraphQL.newGraphQL(GarfieldSchema.GarfieldSchema).build().execute(ei) then: executionResult.data == expectedResult diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy index 1b18fa97f3..b27d042472 100644 --- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryTest.groovy @@ -1,5 +1,6 @@ package graphql.normalized +import graphql.ExecutionInput import graphql.GraphQL import graphql.TestUtil import graphql.execution.CoercedVariables @@ -1314,7 +1315,8 @@ schema { private void assertValidQuery(GraphQLSchema graphQLSchema, String query, Map variables = [:]) { GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build() - assert graphQL.execute(query, null, variables).errors.size() == 0 + def ei = ExecutionInput.newExecutionInput(query).variables(variables).build() + assert graphQL.execute(ei).errors.size() == 0 } def "normalized arguments"() { From a79d6f744a67faa960ed72ca8e4329f32a430eb3 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:03:30 +1000 Subject: [PATCH 105/294] Remove test after method removed --- src/test/groovy/graphql/GraphQLTest.groovy | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 9bece64ecd..af9f0d0595 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -1363,28 +1363,4 @@ many lines'''] then: ! er.errors.isEmpty() } - - def "deprecated execute method works"() { - given: - def query = """ - query FetchSomeIDQuery(\$someId: String!) { - human(id: \$someId) { - name - } - } - """ - def params = [ - someId: '1000' - ] - def expected = [ - human: [ - name: 'Luke Skywalker' - ] - ] - when: - def result = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).build().execute(query, null, params).data // Retain deprecated method for test coverage - - then: - result == expected - } } From 309e66e9d875ea63b8521d553c50075d039a295b Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 24 Aug 2022 12:23:55 +1000 Subject: [PATCH 106/294] Reproduction of https://github.com/graphql-java/graphql-java/issues/2933 --- .../schema/SchemaTransformerTest.groovy | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 6cda5ee323..63a8e102ff 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -870,4 +870,41 @@ type Query { newSchema.getType("Foo") == null (newSchema.getObjectType("Query").getFieldDefinition("field").getType() as GraphQLScalarType).getName() == "Bar" } + + def "rename scalars are changed in applied arguments"() { + + def schema = TestUtil.schema(""" + scalar Foo + directive @myDirective(foo: Foo) on FIELD_DEFINITION + type Query { + field: Foo @myDirective + } +""") + + def visitor = new GraphQLTypeVisitorStub() { + + @Override + TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + if (node.getName().equals("Foo")) { + GraphQLScalarType newNode = node.transform({sc -> sc.name("Bar")}) + return changeNode(context, newNode) + } + return super.visitGraphQLScalarType(node, context) + } + } + + when: + def newSchema = SchemaTransformer.transformSchema(schema, visitor) + then: + newSchema.getType("Bar") instanceof GraphQLScalarType + newSchema.getType("Foo") == null + + def fieldDef = newSchema.getObjectType("Query").getFieldDefinition("field") + (fieldDef.getType() as GraphQLScalarType).getName() == "Bar" + + def appliedDirective = fieldDef.getAppliedDirective("myDirective") + (appliedDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" + + + } } From 63b36b3f10747e9d32dcd12f98fbe147f955d97e Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 24 Aug 2022 12:28:31 +1000 Subject: [PATCH 107/294] Reproduction of https://github.com/graphql-java/graphql-java/issues/2933 Old directive mechanism works --- .../graphql/schema/SchemaTransformerTest.groovy | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 63a8e102ff..d38aaac5ef 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -896,15 +896,18 @@ type Query { when: def newSchema = SchemaTransformer.transformSchema(schema, visitor) then: - newSchema.getType("Bar") instanceof GraphQLScalarType - newSchema.getType("Foo") == null def fieldDef = newSchema.getObjectType("Query").getFieldDefinition("field") - (fieldDef.getType() as GraphQLScalarType).getName() == "Bar" - def appliedDirective = fieldDef.getAppliedDirective("myDirective") + def oldDirective = fieldDef.getDirective("myDirective") + + (fieldDef.getType() as GraphQLScalarType).getName() == "Bar" + (oldDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" (appliedDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" + newSchema.getType("Bar") instanceof GraphQLScalarType + newSchema.getType("Foo") == null + } } From 6509a388c36bb5d4b049b836cf290002af2f7b35 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 24 Aug 2022 12:39:15 +1000 Subject: [PATCH 108/294] Reproduction of https://github.com/graphql-java/graphql-java/issues/2933 Old directive mechanism works as does field arguments --- .../graphql/schema/SchemaTransformerTest.groovy | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index d38aaac5ef..3e88cce5f1 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -877,7 +877,7 @@ type Query { scalar Foo directive @myDirective(foo: Foo) on FIELD_DEFINITION type Query { - field: Foo @myDirective + foo(foo: Foo) : Foo @myDirective } """) @@ -897,17 +897,21 @@ type Query { def newSchema = SchemaTransformer.transformSchema(schema, visitor) then: - def fieldDef = newSchema.getObjectType("Query").getFieldDefinition("field") + def fieldDef = newSchema.getObjectType("Query").getFieldDefinition("foo") def appliedDirective = fieldDef.getAppliedDirective("myDirective") - def oldDirective = fieldDef.getDirective("myDirective") + def oldSkoolDirective = fieldDef.getDirective("myDirective") + def argument = fieldDef.getArgument("foo") + def directive = newSchema.getDirective("myDirective") + def directiveArgument = directive.getArgument("foo") (fieldDef.getType() as GraphQLScalarType).getName() == "Bar" - (oldDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" + (argument.getType() as GraphQLScalarType).getName() == "Bar" + (directiveArgument.getType() as GraphQLScalarType).getName() == "Bar" + + (oldSkoolDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" (appliedDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" newSchema.getType("Bar") instanceof GraphQLScalarType newSchema.getType("Foo") == null - - } } From 0cf5ed94fec03586a7b2f1eafcd54b53bc902086 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 24 Aug 2022 13:00:47 +1000 Subject: [PATCH 109/294] Reproduction of https://github.com/graphql-java/graphql-java/issues/2933 It seems to only be on applied directives --- .../graphql/schema/SchemaTransformerTest.groovy | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 3e88cce5f1..c315553f1b 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -875,9 +875,9 @@ type Query { def schema = TestUtil.schema(""" scalar Foo - directive @myDirective(foo: Foo) on FIELD_DEFINITION + directive @myDirective(fooArgOnDirective: Foo) on FIELD_DEFINITION type Query { - foo(foo: Foo) : Foo @myDirective + foo(fooArgOnField: Foo) : Foo @myDirective } """) @@ -900,18 +900,20 @@ type Query { def fieldDef = newSchema.getObjectType("Query").getFieldDefinition("foo") def appliedDirective = fieldDef.getAppliedDirective("myDirective") def oldSkoolDirective = fieldDef.getDirective("myDirective") - def argument = fieldDef.getArgument("foo") - def directive = newSchema.getDirective("myDirective") - def directiveArgument = directive.getArgument("foo") + def argument = fieldDef.getArgument("fooArgOnField") + def directiveDecl = newSchema.getDirective("myDirective") + def directiveArgument = directiveDecl.getArgument("fooArgOnDirective") (fieldDef.getType() as GraphQLScalarType).getName() == "Bar" (argument.getType() as GraphQLScalarType).getName() == "Bar" (directiveArgument.getType() as GraphQLScalarType).getName() == "Bar" - (oldSkoolDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" - (appliedDirective.getArgument("foo").getType() as GraphQLScalarType).getName() == "Bar" + (oldSkoolDirective.getArgument("fooArgOnDirective").getType() as GraphQLScalarType).getName() == "Bar" newSchema.getType("Bar") instanceof GraphQLScalarType + + // not working at this stage + (appliedDirective.getArgument("fooArgOnDirective").getType() as GraphQLScalarType).getName() == "Bar" newSchema.getType("Foo") == null } } From d9c13996b05a184f53f62d68fb5b1cd343c5dc1b Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 24 Aug 2022 14:10:03 +1000 Subject: [PATCH 110/294] Reproduction of https://github.com/graphql-java/graphql-java/issues/2933 Better toString on Breadcrumbs --- src/main/java/graphql/util/Breadcrumb.java | 10 +++++++++- src/main/java/graphql/util/NodeLocation.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/util/Breadcrumb.java b/src/main/java/graphql/util/Breadcrumb.java index cae637efdc..ad1415a00a 100644 --- a/src/main/java/graphql/util/Breadcrumb.java +++ b/src/main/java/graphql/util/Breadcrumb.java @@ -3,6 +3,7 @@ import graphql.PublicApi; import java.util.Objects; +import java.util.StringJoiner; /** * A specific {@link NodeLocation} inside a node. This means {@link #getNode()} returns a Node which has a child @@ -47,9 +48,16 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = 1; - result = 31 * result + Objects.hashCode(node); + result = 31 * result + Objects.hashCode(node); result = 31 * result + Objects.hashCode(location); return result; } + @Override + public String toString() { + return new StringJoiner(", ", "[", "]") + .add("" + location) + .add("" + node) + .toString(); + } } diff --git a/src/main/java/graphql/util/NodeLocation.java b/src/main/java/graphql/util/NodeLocation.java index 61851b0b6f..8a1f9f10bc 100644 --- a/src/main/java/graphql/util/NodeLocation.java +++ b/src/main/java/graphql/util/NodeLocation.java @@ -33,7 +33,7 @@ public int getIndex() { @Override public String toString() { - return "NodeLocation{" + + return "{" + "name='" + name + '\'' + ", index=" + index + '}'; From 431affc8aae4e3d105feda2e98fb99cd8fe5c8da Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 24 Aug 2022 15:59:11 +1000 Subject: [PATCH 111/294] fix missing GraphQLAppliedDirectiveArgument: type child was not considered --- .../schema/GraphQLAppliedDirectiveArgument.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java index dd595a5392..5942425204 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -34,6 +35,8 @@ public class GraphQLAppliedDirectiveArgument implements GraphQLNamedSchemaElemen private final Argument definition; + public static final String CHILD_TYPE = "type"; + private GraphQLAppliedDirectiveArgument(String name, InputValueWithState value, GraphQLInputType type, @@ -104,19 +107,23 @@ public Argument getDefinition() { @Override public List getChildren() { - return ImmutableKit.emptyList(); + List children = new ArrayList<>(); + children.add(getType()); + return children; } - @Override public SchemaElementChildrenContainer getChildrenWithTypeReferences() { return SchemaElementChildrenContainer.newSchemaElementChildrenContainer() + .child(CHILD_TYPE, originalType) .build(); } @Override public GraphQLAppliedDirectiveArgument withNewChildren(SchemaElementChildrenContainer newChildren) { - return this; + return transform(builder -> + builder.type(newChildren.getChildOrNull(CHILD_TYPE)) + ); } @Override From cb57c0e5fdf164a7ad20832662f0a45c3e5f2c40 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Thu, 25 Aug 2022 11:53:12 +1000 Subject: [PATCH 112/294] Diff counts are the same See https://github.com/graphql-java/graphql-java/issues/2877 --- .../java/graphql/schema/diff/SchemaDiff.java | 2 +- .../graphql/schema/diff/SchemaDiffTest.groovy | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diff/SchemaDiff.java b/src/main/java/graphql/schema/diff/SchemaDiff.java index 52c2fa0747..b89616bc40 100644 --- a/src/main/java/graphql/schema/diff/SchemaDiff.java +++ b/src/main/java/graphql/schema/diff/SchemaDiff.java @@ -70,7 +70,7 @@ public static Options defaultOptions() { private static class CountingReporter implements DifferenceReporter { final DifferenceReporter delegate; - int breakingCount = 1; + int breakingCount = 0; private CountingReporter(DifferenceReporter delegate) { this.delegate = delegate; diff --git a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy index ca957db197..07d1350258 100644 --- a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy +++ b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy @@ -570,4 +570,25 @@ class SchemaDiffTest extends Specification { } + def "SchemaDiff and CapturingReporter have the same diff counts"() { + def schema1 = TestUtil.schema("type Query { f : String }") + def schema2 = TestUtil.schema("type Query { f : Int }") + + when: + def capturingReporter = new CapturingReporter() + def schemaDiff = new SchemaDiff() + def breakingCount = schemaDiff.diffSchema(DiffSet.diffSet(schema1, schema1), capturingReporter) + then: + breakingCount == capturingReporter.getBreakageCount() + breakingCount == 0 + + when: + capturingReporter = new CapturingReporter() + schemaDiff = new SchemaDiff() + breakingCount = schemaDiff.diffSchema(DiffSet.diffSet(schema1, schema2), capturingReporter) + + then: + breakingCount == capturingReporter.getBreakageCount() + breakingCount == 1 + } } From f9b878b60170e948f52f280d3e60a0818ca26a50 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 27 Aug 2022 12:55:12 +1000 Subject: [PATCH 113/294] Remove redundant NaN check, already handled in GraphqlFloatCoercing --- src/main/java/graphql/execution/ExecutionStrategy.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 4008b6e00a..41d80eccf1 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -599,11 +599,6 @@ protected CompletableFuture completeValueForScalar(ExecutionCon serialized = handleCoercionProblem(executionContext, parameters, e); } - // TODO: fix that: this should not be handled here - //6.6.1 http://facebook.github.io/graphql/#sec-Field-entries - if (serialized instanceof Double && ((Double) serialized).isNaN()) { - serialized = null; - } try { serialized = parameters.getNonNullFieldValidator().validate(parameters.getPath(), serialized); } catch (NonNullableFieldWasNullException e) { From b4b51749227b2e118450cfcfc365218c7ebafe8f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:27:08 +1000 Subject: [PATCH 114/294] Tidy up todos --- src/test/resources/extra-large-schema-1.graphqls | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/resources/extra-large-schema-1.graphqls b/src/test/resources/extra-large-schema-1.graphqls index 4e7ba47bf8..0d32ac9cca 100644 --- a/src/test/resources/extra-large-schema-1.graphqls +++ b/src/test/resources/extra-large-schema-1.graphqls @@ -824,12 +824,10 @@ enum JiraAttachmentsPermissions { input JiraOrderDirection { id: ID - #TODO } input JiraAttachmentsOrderField { id: ID - #TODO } """ Represents the avatar size urls. @@ -1655,7 +1653,6 @@ type JiraComponent implements Node { Component description. """ description: String - # TODO: lead, default assignee ... } """ From 93d7f5df0db26f4e55506a97d69c7ce537e8638e Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Wed, 31 Aug 2022 08:21:57 +1000 Subject: [PATCH 115/294] Make parseValue nullable and update Coercing javadoc (#2938) * Make parseValue nullable and update doco * Add DeprecatedAt --- src/main/java/graphql/schema/Coercing.java | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/Coercing.java b/src/main/java/graphql/schema/Coercing.java index 2f66ae83ca..96a31947e8 100644 --- a/src/main/java/graphql/schema/Coercing.java +++ b/src/main/java/graphql/schema/Coercing.java @@ -1,6 +1,7 @@ package graphql.schema; +import graphql.DeprecatedAt; import graphql.GraphQLContext; import graphql.PublicSpi; import graphql.execution.CoercedVariables; @@ -52,6 +53,7 @@ public interface Coercing { * @throws graphql.schema.CoercingSerializeException if value input can't be serialized */ @Deprecated + @DeprecatedAt("2022-08-22") @Nullable O serialize(@NotNull Object dataFetcherResult) throws CoercingSerializeException; /** @@ -77,21 +79,23 @@ public interface Coercing { return serialize(dataFetcherResult); } - /** * Called to resolve an input from a query variable into a Java object acceptable for the scalar type. *

* Note : You should not allow {@link java.lang.RuntimeException}s to come out of your parseValue method, but rather * catch them and fire them as {@link graphql.schema.CoercingParseValueException} instead as per the method contract. * + * Note : if input is explicit/raw value null, input coercion will return null before this method is called + * * @param input is never null * - * @return a parsed value which is never null + * @return a parsed value which may be null * * @throws graphql.schema.CoercingParseValueException if value input can't be parsed */ @Deprecated - @NotNull I parseValue(@NotNull Object input) throws CoercingParseValueException; + @DeprecatedAt("2022-08-22") + @Nullable I parseValue(@NotNull Object input) throws CoercingParseValueException; /** * Called to resolve an input from a query variable into a Java object acceptable for the scalar type. @@ -99,15 +103,17 @@ public interface Coercing { * Note : You should not allow {@link java.lang.RuntimeException}s to come out of your parseValue method, but rather * catch them and fire them as {@link graphql.schema.CoercingParseValueException} instead as per the method contract. * + * Note : if input is explicit/raw value null, input coercion will return null before this method is called + * * @param input is never null * @param graphQLContext the graphql context in place * @param locale the locale to use * - * @return a parsed value which is never null + * @return a parsed value which may be null * * @throws graphql.schema.CoercingParseValueException if value input can't be parsed */ - default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { + @Nullable default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { assertNotNull(input); assertNotNull(graphQLContext); assertNotNull(locale); @@ -121,13 +127,16 @@ default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLConte * Note : You should not allow {@link java.lang.RuntimeException}s to come out of your parseLiteral method, but rather * catch them and fire them as {@link graphql.schema.CoercingParseLiteralException} instead as per the method contract. * + * Note : if input is literal {@link graphql.language.NullValue}, input coercion will return null before this method is called + * * @param input is never null * - * @return a parsed value which is never null + * @return a parsed value which may be null * * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed */ @Deprecated + @DeprecatedAt("2022-08-22") @Nullable I parseLiteral(@NotNull Object input) throws CoercingParseLiteralException; /** @@ -141,15 +150,18 @@ default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLConte * objects and convert them into actual values. But for those scalar types that want to do this, then this * method should be implemented. * + * Note : if input is literal {@link graphql.language.NullValue}, input coercion will return null before this method is called + * * @param input is never null * @param variables the resolved variables passed to the query * - * @return a parsed value which is never null + * @return a parsed value which may be null * * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed */ @SuppressWarnings("unused") @Deprecated + @DeprecatedAt("2022-08-22") default @Nullable I parseLiteral(Object input, Map variables) throws CoercingParseLiteralException { return parseLiteral(input); } @@ -165,12 +177,14 @@ default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLConte * objects and convert them into actual values. But for those scalar types that want to do this, then this * method should be implemented. * + * Note : if input is literal {@link graphql.language.NullValue}, input coercion will return null before this method is called + * * @param input is never null * @param variables the resolved variables passed to the query * @param graphQLContext the graphql context in place * @param locale the locale to use * - * @return a parsed value which may be null, say for {@link graphql.language.NullValue} as input + * @return a parsed value which may be null * * @throws graphql.schema.CoercingParseLiteralException if input literal can't be parsed */ @@ -192,6 +206,7 @@ default I parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLConte * @return The literal matching the external input value. */ @Deprecated + @DeprecatedAt("2022-08-22") default @NotNull Value valueToLiteral(@NotNull Object input) { throw new UnsupportedOperationException("This is not implemented by this Scalar " + this.getClass()); } From c01fc2795a4d29840db2f2206386c66d3331ac42 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:55:44 +1000 Subject: [PATCH 116/294] Make English messages consistent, no full stop at end of message --- src/main/resources/i18n/Scalars.properties | 12 ++++++------ .../rules/ArgumentsOfCorrectTypeTest.groovy | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/resources/i18n/Scalars.properties b/src/main/resources/i18n/Scalars.properties index d392ad798e..0897fe58eb 100644 --- a/src/main/resources/i18n/Scalars.properties +++ b/src/main/resources/i18n/Scalars.properties @@ -10,20 +10,20 @@ # REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them # so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' # -Scalar.unexpectedAstType=Expected an AST type of ''{0}'' but it was a ''{1}''. +Scalar.unexpectedAstType=Expected an AST type of ''{0}'' but it was a ''{1}'' # -Enum.badInput=Invalid input for enum ''{0}''. Unknown value ''{1}''. +Enum.badInput=Invalid input for enum ''{0}''. Unknown value ''{1}'' Enum.badName=Invalid input for enum ''{0}''. No value found for name ''{1}'' -Enum.unallowableValue=Literal value not in allowable values for enum ''{0}'' - ''{1}''. +Enum.unallowableValue=Literal value not in allowable values for enum ''{0}'' - ''{1}'' # Int.notInt=Expected a value that can be converted to type ''Int'' but it was a ''{0}'' Int.outsideRange=Expected value to be in the integer range, but it was a ''{0}'' # ID.notId=Expected a value that can be converted to type ''ID'' but it was a ''{0}'' -ID.unexpectedAstType=Expected an AST type of ''IntValue'' or ''StringValue'' but it was a ''{0}''. +ID.unexpectedAstType=Expected an AST type of ''IntValue'' or ''StringValue'' but it was a ''{0}'' # Float.notFloat=Expected a value that can be converted to type ''Float'' but it was a ''{0}'' -Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' but it was a ''{0}''. +Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' but it was a ''{0}'' # Boolean.notBoolean=Expected a value that can be converted to type ''Boolean'' but it was a ''{0}'' -Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}''. +Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}'' diff --git a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy index 513e2884a5..67c87407cf 100644 --- a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy @@ -80,7 +80,7 @@ class ArgumentsOfCorrectTypeTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType - validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'arg1' with value 'IntValue{value=1}' is not a valid 'String' - Expected an AST type of 'StringValue' but it was a 'IntValue'." + validationErrors.get(0).message == "Validation error (WrongType@[dog]) : argument 'arg1' with value 'IntValue{value=1}' is not a valid 'String' - Expected an AST type of 'StringValue' but it was a 'IntValue'" } def "invalid input object type results in error"() { @@ -332,7 +332,7 @@ class ArgumentsOfCorrectTypeTest extends Specification { !validationErrors.empty validationErrors.size() == 1 validationErrors.get(0).getValidationErrorType() == ValidationErrorType.WrongType - validationErrors.get(0).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'EnumValue{name='PRETTY'}' is not a valid 'DogCommand' - Literal value not in allowable values for enum 'DogCommand' - 'EnumValue{name='PRETTY'}'." + validationErrors.get(0).message == "Validation error (WrongType@[dog/doesKnowCommand]) : argument 'dogCommand' with value 'EnumValue{name='PRETTY'}' is not a valid 'DogCommand' - Literal value not in allowable values for enum 'DogCommand' - 'EnumValue{name='PRETTY'}'" } static List validate(String query) { From 453255fa13b50d7896fef78fe7a1274b6053865c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:56:25 +1000 Subject: [PATCH 117/294] Add German translations for parsing and scalar messages --- src/main/resources/i18n/Parsing_de.properties | 30 +++++++++++++++++ src/main/resources/i18n/Scalars_de.properties | 32 +++++++++++++++++++ .../resources/i18n/Validation_de.properties | 3 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/i18n/Parsing_de.properties create mode 100644 src/main/resources/i18n/Scalars_de.properties diff --git a/src/main/resources/i18n/Parsing_de.properties b/src/main/resources/i18n/Parsing_de.properties new file mode 100644 index 0000000000..c8569e1b73 --- /dev/null +++ b/src/main/resources/i18n/Parsing_de.properties @@ -0,0 +1,30 @@ +# +# This resource bundle is used for the query parsing code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +# Prior to Java 9, properties files are encoded in ISO-8859-1. +# We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss +# +# TODO: uncomment these messages after the default (English) Parsing bundle is merged in +#InvalidSyntax.noMessage=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} +#InvalidSyntax.full=Ung\u00fcltige Syntax mit ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} +# +#InvalidSyntaxBail.noToken=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} +#InvalidSyntaxBail.full=Ung\u00fcltige Syntax mit ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} +## +#InvalidSyntaxMoreTokens.full=Es wurde eine ung\u00fcltige Syntax festgestellt. Es gibt zus\u00e4tzliche Token im Text, die nicht konsumiert wurden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +## +#ParseCancelled.full=Es wurden mehr als {0} ''{1}'' Token pr\u00e4sentiert. Um Denial-of-Service-Angriffe zu verhindern, wurde das Parsing abgebrochen +## +#InvalidUnicode.trailingLeadingSurrogate=Ung\u00fcltiger Unicode gefunden. Nachfolgendes Surrogat muss ein f\u00fchrendes Surrogat vorangestellt werden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +#InvalidUnicode.leadingTrailingSurrogate=Ung\u00fcltiger Unicode gefunden. Auf ein f\u00fchrendes Surrogat muss ein abschlie\u00dfendes Surrogat folgen. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +#InvalidUnicode.invalidCodePoint=Ung\u00fcltiger Unicode gefunden. Kein g\u00fcltiger Codepunkt. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +#InvalidUnicode.incorrectEscape=Ung\u00fcltiger Unicode gefunden. Falsch formatierte Escape-Sequenz. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} \ No newline at end of file diff --git a/src/main/resources/i18n/Scalars_de.properties b/src/main/resources/i18n/Scalars_de.properties new file mode 100644 index 0000000000..cbe1dee82d --- /dev/null +++ b/src/main/resources/i18n/Scalars_de.properties @@ -0,0 +1,32 @@ +# +# This resource bundle is used for the scalar code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +# Prior to Java 9, properties files are encoded in ISO-8859-1. +# We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss +# +Scalar.unexpectedAstType=Erwartet wurde ein AST-Typ von ''{0}'', aber es war ein ''{1}'' +# +Enum.badInput=Ung\u00fcltige Eingabe f\u00fcr enum ''{0}''. Unbekannter Wert ''{1}'' +Enum.badName=Ung\u00fcltige Eingabe f\u00fcr enum ''{0}''. Kein Wert f\u00fcr den Namen ''{1}'' gefunden +Enum.unallowableValue=Literaler Wert nicht in den zul\u00e4ssigen Werten f\u00fcr enum ''{0}'' - ''{1}'' +# +Int.notInt=Erwartet wurde ein Wert, der in den Typ ''Int'' konvertiert werden kann, aber es war ein ''{0}'' +Int.outsideRange=Erwarteter Wert im Integer-Bereich, aber es war ein ''{0}'' +# +ID.notId=Erwartet wurde ein Wert, der in den Typ ''ID'' umgewandelt werden kann, aber es war ein ''{0}'' +ID.unexpectedAstType=Erwartet wurde ein AST-Typ von ''IntValue'' oder ''StringValue'', aber es war ein ''{0}'' +# +Float.notFloat=Erwartet wurde ein Wert, der in den Typ ''Float'' konvertiert werden kann, aber es war ein ''{0}'' +Float.unexpectedAstType=Erwartet wurde ein AST-Typ von ''IntValue'' oder ''FloatValue'', aber es war ein ''{0}'' +# +Boolean.notBoolean=Erwartet wurde ein Wert, der in den Typ ''Boolean'' konvertiert werden kann, aber es war ein ''{0}'' +Boolean.unexpectedAstType=Erwartet wurde ein AST-Typ ''BooleanValue'', aber es war ein ''{0}'' diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 591dd5d4e7..1ab468df7a 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -11,7 +11,8 @@ # so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' # # Prior to Java 9, properties files are encoded in ISO-8859-1. -# Leider, we have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe +# We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss +# ExecutableDefinitions.notExecutableType=Validierungsfehler ({0}) : Die Typdefinition ''{1}'' ist nicht ausf\u00fchrbar ExecutableDefinitions.notExecutableSchema=Validierungsfehler ({0}) : Die Schemadefinition ist nicht ausf\u00fchrbar ExecutableDefinitions.notExecutableDirective=Validierungsfehler ({0}) : Die Direktivendefinition ''{1}'' ist nicht ausf\u00fchrbar From 96f5bf7932cd42408f8940b3b0309c5cd782af23 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 4 Sep 2022 12:14:47 +1000 Subject: [PATCH 118/294] Cleaning up tests with deprecated usage: Part 2 (#2941) * Fix first wave of applied argument tests * Fix up deprecated coercing tests * Fix applied directive tests * More argument test fixes --- src/main/java/graphql/schema/Coercing.java | 6 +- .../GraphQLAppliedDirectiveArgument.java | 2 +- .../java/graphql/schema/GraphQLSchema.java | 2 +- .../groovy/graphql/ExecutionInputTest.groovy | 12 +-- src/test/groovy/graphql/Issue296.groovy | 84 ------------------- .../groovy/graphql/ScalarsBooleanTest.groovy | 45 ++++++++-- .../groovy/graphql/ScalarsFloatTest.groovy | 51 +++++++++-- src/test/groovy/graphql/ScalarsIDTest.groovy | 21 +++-- src/test/groovy/graphql/ScalarsIntTest.groovy | 55 +++++++++--- .../groovy/graphql/ScalarsStringTest.groovy | 32 +++++-- .../graphql/TypeResolverExecutionTest.groovy | 3 - .../execution/ValuesResolverTest.groovy | 9 +- .../directives/QueryDirectivesImplTest.groovy | 4 +- .../introspection/IntrospectionTest.groovy | 84 ++++++++++++++++++- ...raphQLAppliedDirectiveArgumentTest.groovy} | 26 +++--- .../graphql/schema/GraphQLArgumentTest.groovy | 23 ++--- .../schema/GraphQLDirectiveTest.groovy | 43 +++++----- .../graphql/schema/GraphQLEnumTypeTest.groovy | 45 +++++++--- ...GeneratorAppliedDirectiveHelperTest.groovy | 20 ----- .../schema/idl/SchemaPrinterTest.groovy | 62 ++++++++++---- .../TypesImplementInterfacesTest.groovy | 14 ++-- .../rules/ProvidedNonNullArgumentsTest.groovy | 4 +- 22 files changed, 410 insertions(+), 237 deletions(-) delete mode 100644 src/test/groovy/graphql/Issue296.groovy rename src/test/groovy/graphql/{Issue2001.groovy => schema/GraphQLAppliedDirectiveArgumentTest.groovy} (73%) diff --git a/src/main/java/graphql/schema/Coercing.java b/src/main/java/graphql/schema/Coercing.java index 96a31947e8..491bd16b0e 100644 --- a/src/main/java/graphql/schema/Coercing.java +++ b/src/main/java/graphql/schema/Coercing.java @@ -15,7 +15,7 @@ import static graphql.Assert.assertNotNull; /** - * The Coercing interface is used by {@link graphql.schema.GraphQLScalarType}s to parse and serialise object values. + * The Coercing interface is used by {@link graphql.schema.GraphQLScalarType}s to parse and serialize object values. *

* There are two major responsibilities, result coercion and input coercion. *

@@ -41,7 +41,7 @@ public interface Coercing { * Called to convert a Java object result of a DataFetcher to a valid runtime value for the scalar type. *

* Note : Throw {@link graphql.schema.CoercingSerializeException} if there is fundamental - * problem during serialisation, don't return null to indicate failure. + * problem during serialization, don't return null to indicate failure. *

* Note : You should not allow {@link java.lang.RuntimeException}s to come out of your serialize method, but rather * catch them and fire them as {@link graphql.schema.CoercingSerializeException} instead as per the method contract. @@ -60,7 +60,7 @@ public interface Coercing { * Called to convert a Java object result of a DataFetcher to a valid runtime value for the scalar type. *

* Note : Throw {@link graphql.schema.CoercingSerializeException} if there is fundamental - * problem during serialisation, don't return null to indicate failure. + * problem during serialization, don't return null to indicate failure. *

* Note : You should not allow {@link java.lang.RuntimeException}s to come out of your serialize method, but rather * catch them and fire them as {@link graphql.schema.CoercingSerializeException} instead as per the method contract. diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java index 2aa836c828..276e4f4102 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java @@ -76,7 +76,7 @@ public boolean hasSetValue() { } /** - * This swill give out an internal java value based on the semantics captured + * This will give out an internal java value based on the semantics captured * in the {@link InputValueWithState} from {@link GraphQLAppliedDirectiveArgument#getArgumentValue()} * * Note : You MUST only call this on a {@link GraphQLAppliedDirectiveArgument} that is part of a fully formed schema. We need diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 17bc1fae39..adbf61aaf1 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -476,7 +476,7 @@ public List getSchemaDirectives() { * directives for all schema elements, whereas this is just for the schema * element itself * - * @return a map of directives + * @return a map of directives * * @deprecated Use the {@link GraphQLAppliedDirective} methods instead */ diff --git a/src/test/groovy/graphql/ExecutionInputTest.groovy b/src/test/groovy/graphql/ExecutionInputTest.groovy index f29b637f53..f0b4b232fc 100644 --- a/src/test/groovy/graphql/ExecutionInputTest.groovy +++ b/src/test/groovy/graphql/ExecutionInputTest.groovy @@ -49,17 +49,17 @@ class ExecutionInputTest extends Specification { // Retaining deprecated method tests for coverage when: def executionInput = ExecutionInput.newExecutionInput().query(query) - .context({ builder -> builder.of("k1", "v1") } as UnaryOperator) + .context({ builder -> builder.of("k1", "v1") } as UnaryOperator) // Retain deprecated for test coverage .build() then: - (executionInput.context as GraphQLContext).get("k1") == "v1" + (executionInput.context as GraphQLContext).get("k1") == "v1" // Retain deprecated for test coverage when: executionInput = ExecutionInput.newExecutionInput().query(query) - .context(GraphQLContext.newContext().of("k2", "v2")) + .context(GraphQLContext.newContext().of("k2", "v2")) // Retain deprecated for test coverage .build() then: - (executionInput.context as GraphQLContext).get("k2") == "v2" + (executionInput.context as GraphQLContext).get("k2") == "v2" // Retain deprecated for test coverage } def "legacy context is defaulted"() { @@ -68,8 +68,8 @@ class ExecutionInputTest extends Specification { def executionInput = ExecutionInput.newExecutionInput().query(query) .build() then: - executionInput.context instanceof GraphQLContext - executionInput.getGraphQLContext() == executionInput.getContext() + executionInput.context instanceof GraphQLContext // Retain deprecated for test coverage + executionInput.getGraphQLContext() == executionInput.getContext() // Retain deprecated for test coverage } def "graphql context is defaulted"() { diff --git a/src/test/groovy/graphql/Issue296.groovy b/src/test/groovy/graphql/Issue296.groovy deleted file mode 100644 index e0ccc13549..0000000000 --- a/src/test/groovy/graphql/Issue296.groovy +++ /dev/null @@ -1,84 +0,0 @@ -package graphql - -import spock.lang.Specification - -import static graphql.Scalars.GraphQLString -import static graphql.schema.GraphQLArgument.newArgument -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition -import static graphql.schema.GraphQLInputObjectField.newInputObjectField -import static graphql.schema.GraphQLInputObjectType.newInputObject -import static graphql.schema.GraphQLObjectType.newObject -import static graphql.schema.GraphQLSchema.newSchema - -class Issue296 extends Specification { - - def "test introspection for #296 with map"() { - - def graphql = GraphQL.newGraphQL(newSchema() - .query(newObject() - .name("Query") - .field(newFieldDefinition() - .name("field") - .type(GraphQLString) - .argument(newArgument() - .name("argument") - .type(newInputObject() - .name("InputObjectType") - .field(newInputObjectField() - .name("inputField") - .type(GraphQLString)) - .build()) - .defaultValue([inputField: 'value1'])))) - .build()) - .build() - - def query = '{ __type(name: "Query") { fields { args { defaultValue } } } }' - - expect: - // converts the default object value to AST, then graphql pretty prints that as the value - graphql.execute(query).data == - [__type: [fields: [[args: [[defaultValue: '{inputField : "value1"}']]]]]] - } - - class FooBar { - final String inputField = "foo" - final String bar = "bar" - - String getInputField() { - return inputField - } - - String getBar() { - return bar - } - } - - def "test introspection for #296 with some object"() { - - def graphql = GraphQL.newGraphQL(newSchema() - .query(newObject() - .name("Query") - .field(newFieldDefinition() - .name("field") - .type(GraphQLString) - .argument(newArgument() - .name("argument") - .type(newInputObject() - .name("InputObjectType") - .field(newInputObjectField() - .name("inputField") - .type(GraphQLString)) - .build()) - .defaultValue(new FooBar())))) - .build()) - .build() - - def query = '{ __type(name: "Query") { fields { args { defaultValue } } } }' - - expect: - // converts the default object value to AST, then graphql pretty prints that as the value - graphql.execute(query).data == - [__type: [fields: [[args: [[defaultValue: '{inputField : "foo"}']]]]]] - } -} - diff --git a/src/test/groovy/graphql/ScalarsBooleanTest.groovy b/src/test/groovy/graphql/ScalarsBooleanTest.groovy index a3ce05f46c..c059d5c53c 100644 --- a/src/test/groovy/graphql/ScalarsBooleanTest.groovy +++ b/src/test/groovy/graphql/ScalarsBooleanTest.groovy @@ -1,5 +1,6 @@ package graphql +import graphql.execution.CoercedVariables import graphql.language.BooleanValue import graphql.language.FloatValue import graphql.language.IntValue @@ -15,19 +16,29 @@ class ScalarsBooleanTest extends Specification { @Unroll def "Boolean parse literal #literal.value as #result"() { expect: - Scalars.GraphQLBoolean.getCoercing().parseLiteral(literal) == result + Scalars.GraphQLBoolean.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) == result where: literal | result new BooleanValue(true) | true new BooleanValue(false) | false + } + + @Unroll + def "Boolean parse literal #literal.value as #result with deprecated method"() { + expect: + Scalars.GraphQLBoolean.getCoercing().parseLiteral(literal) == result // Retain deprecated method for test coverage + where: + literal | result + new BooleanValue(true) | true + new BooleanValue(false) | false } @Unroll def "Boolean returns null for invalid #literal"() { when: - Scalars.GraphQLBoolean.getCoercing().parseLiteral(literal) + Scalars.GraphQLBoolean.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) then: thrown(CoercingParseLiteralException) @@ -41,8 +52,30 @@ class ScalarsBooleanTest extends Specification { @Unroll def "Boolean serialize #value into #result (#result.class)"() { expect: - Scalars.GraphQLBoolean.getCoercing().serialize(value) == result - Scalars.GraphQLBoolean.getCoercing().parseValue(value) == result + Scalars.GraphQLBoolean.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result + Scalars.GraphQLBoolean.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + true | true + "false" | false + "true" | true + "True" | true + 0 | false + 1 | true + -1 | true + new Long(42345784398534785l) | true + new Double(42.3) | true + new Float(42.3) | true + Integer.MAX_VALUE + 1l | true + Integer.MIN_VALUE - 1l | true + } + + @Unroll + def "Boolean serialize #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLBoolean.getCoercing().serialize(value) == result // Retain deprecated method for test coverage + Scalars.GraphQLBoolean.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage where: value | result @@ -63,7 +96,7 @@ class ScalarsBooleanTest extends Specification { @Unroll def "serialize throws exception for invalid input #value"() { when: - Scalars.GraphQLBoolean.getCoercing().serialize(value) + Scalars.GraphQLBoolean.getCoercing().serialize(value, GraphQLContext.default, Locale.default) then: thrown(CoercingSerializeException) @@ -81,7 +114,7 @@ class ScalarsBooleanTest extends Specification { @Unroll def "parseValue throws exception for invalid input #value"() { when: - Scalars.GraphQLBoolean.getCoercing().parseValue(value) + Scalars.GraphQLBoolean.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) then: thrown(CoercingParseValueException) diff --git a/src/test/groovy/graphql/ScalarsFloatTest.groovy b/src/test/groovy/graphql/ScalarsFloatTest.groovy index 910b731bc9..03de6335dc 100644 --- a/src/test/groovy/graphql/ScalarsFloatTest.groovy +++ b/src/test/groovy/graphql/ScalarsFloatTest.groovy @@ -1,5 +1,6 @@ package graphql +import graphql.execution.CoercedVariables import graphql.language.BooleanValue import graphql.language.FloatValue import graphql.language.IntValue @@ -17,7 +18,7 @@ class ScalarsFloatTest extends Specification { @Unroll def "Float parse literal #literal.value as #result"() { expect: - Scalars.GraphQLFloat.getCoercing().parseLiteral(literal) == result + Scalars.GraphQLFloat.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) == result where: literal | result @@ -25,13 +26,25 @@ class ScalarsFloatTest extends Specification { new FloatValue(Double.MAX_VALUE) | Double.MAX_VALUE new FloatValue(Double.MIN_VALUE) | Double.MIN_VALUE new IntValue(12345678910 as BigInteger) | 12345678910 + } + @Unroll + def "Float parse literal #literal.value as #result with deprecated method"() { + expect: + Scalars.GraphQLFloat.getCoercing().parseLiteral(literal) == result // Retain deprecated for test coverage + + where: + literal | result + new FloatValue(42.442 as BigDecimal) | 42.442 + new FloatValue(Double.MAX_VALUE) | Double.MAX_VALUE + new FloatValue(Double.MIN_VALUE) | Double.MIN_VALUE + new IntValue(12345678910 as BigInteger) | 12345678910 } @Unroll def "Float returns null for invalid #literal"() { when: - Scalars.GraphQLFloat.getCoercing().parseLiteral(literal) + Scalars.GraphQLFloat.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) then: thrown(CoercingParseLiteralException) @@ -44,8 +57,34 @@ class ScalarsFloatTest extends Specification { @Unroll def "Float serialize #value into #result (#result.class)"() { expect: - Scalars.GraphQLFloat.getCoercing().serialize(value) == result - Scalars.GraphQLFloat.getCoercing().parseValue(value) == result + Scalars.GraphQLFloat.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result + Scalars.GraphQLFloat.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + "42" | 42d + "42.123" | 42.123d + 42.0000d | 42 + new Integer(42) | 42 + "-1" | -1 + new BigInteger("42") | 42 + new BigDecimal("42") | 42 + new BigDecimal("4.2") | 4.2d + 42.3f | 42.3d + 42.0d | 42d + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567d + new AtomicInteger(42) | 42 + Double.MAX_VALUE | Double.MAX_VALUE + Double.MIN_VALUE | Double.MIN_VALUE + } + + @Unroll + def "Float serialize #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLFloat.getCoercing().serialize(value) == result // Retain deprecated method for coverage + Scalars.GraphQLFloat.getCoercing().parseValue(value) == result // Retain deprecated method for coverage where: value | result @@ -70,7 +109,7 @@ class ScalarsFloatTest extends Specification { @Unroll def "serialize throws exception for invalid input #value"() { when: - Scalars.GraphQLFloat.getCoercing().serialize(value) + Scalars.GraphQLFloat.getCoercing().serialize(value, GraphQLContext.default, Locale.default) then: thrown(CoercingSerializeException) @@ -84,7 +123,7 @@ class ScalarsFloatTest extends Specification { @Unroll def "serialize/parseValue throws exception for invalid input #value"() { when: - Scalars.GraphQLFloat.getCoercing().parseValue(value) + Scalars.GraphQLFloat.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) then: thrown(CoercingParseValueException) diff --git a/src/test/groovy/graphql/ScalarsIDTest.groovy b/src/test/groovy/graphql/ScalarsIDTest.groovy index 8a9e21362f..a304251796 100644 --- a/src/test/groovy/graphql/ScalarsIDTest.groovy +++ b/src/test/groovy/graphql/ScalarsIDTest.groovy @@ -1,5 +1,6 @@ package graphql +import graphql.execution.CoercedVariables import graphql.language.BooleanValue import graphql.language.IntValue import graphql.language.StringValue @@ -14,19 +15,29 @@ class ScalarsIDTest extends Specification { @Unroll def "ID parse literal #literal.value as #result"() { expect: - Scalars.GraphQLID.getCoercing().parseLiteral(literal) == result + Scalars.GraphQLID.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) == result where: literal | result new StringValue("1234ab") | "1234ab" new IntValue(123 as BigInteger) | "123" + } + + @Unroll + def "ID parse literal #literal.value as #result with deprecated method"() { + expect: + Scalars.GraphQLID.getCoercing().parseLiteral(literal) == result // Retain deprecated method for test coverage + where: + literal | result + new StringValue("1234ab") | "1234ab" + new IntValue(123 as BigInteger) | "123" } @Unroll def "ID returns null for invalid #literal"() { when: - Scalars.GraphQLID.getCoercing().parseLiteral(literal) + Scalars.GraphQLID.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) then: thrown(CoercingParseLiteralException) @@ -36,10 +47,10 @@ class ScalarsIDTest extends Specification { } @Unroll - def "ID serialize #value into #result (#result.class)"() { + def "ID serialize #value into #result (#result.class) with deprecated methods"() { expect: - Scalars.GraphQLID.getCoercing().serialize(value) == result - Scalars.GraphQLID.getCoercing().parseValue(value) == result + Scalars.GraphQLID.getCoercing().serialize(value) == result // Retain deprecated method for test coverage + Scalars.GraphQLID.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage where: value | result diff --git a/src/test/groovy/graphql/ScalarsIntTest.groovy b/src/test/groovy/graphql/ScalarsIntTest.groovy index 2768b23654..883d9dbf17 100644 --- a/src/test/groovy/graphql/ScalarsIntTest.groovy +++ b/src/test/groovy/graphql/ScalarsIntTest.groovy @@ -1,5 +1,6 @@ package graphql +import graphql.execution.CoercedVariables import graphql.language.FloatValue import graphql.language.IntValue import graphql.language.StringValue @@ -16,20 +17,31 @@ class ScalarsIntTest extends Specification { @Unroll def "Int parse literal #literal.value as #result"() { expect: - Scalars.GraphQLInt.getCoercing().parseLiteral(literal) == result + Scalars.GraphQLInt.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) == result where: literal | result new IntValue(42 as BigInteger) | 42 new IntValue(Integer.MAX_VALUE as BigInteger) | Integer.MAX_VALUE new IntValue(Integer.MIN_VALUE as BigInteger) | Integer.MIN_VALUE + } + @Unroll + def "Int parse literal #literal.value as #result with deprecated method"() { + expect: + Scalars.GraphQLInt.getCoercing().parseLiteral(literal) == result // Retain deprecated method for test coverage + + where: + literal | result + new IntValue(42 as BigInteger) | 42 + new IntValue(Integer.MAX_VALUE as BigInteger) | Integer.MAX_VALUE + new IntValue(Integer.MIN_VALUE as BigInteger) | Integer.MIN_VALUE } @Unroll def "Int returns null for invalid #literal"() { when: - Scalars.GraphQLInt.getCoercing().parseLiteral(literal) + Scalars.GraphQLInt.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) then: thrown(CoercingParseLiteralException) @@ -42,14 +54,38 @@ class ScalarsIntTest extends Specification { new IntValue(Integer.MIN_VALUE - 1l as BigInteger) | _ new StringValue("-1") | _ new FloatValue(42.3) | _ - } @Unroll def "Int serialize #value into #result (#result.class)"() { expect: - Scalars.GraphQLInt.getCoercing().serialize(value) == result - Scalars.GraphQLInt.getCoercing().parseValue(value) == result + Scalars.GraphQLInt.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result + Scalars.GraphQLInt.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + "42" | 42 + "42.0000" | 42 + 42.0000d | 42 + new Integer(42) | 42 + "-1" | -1 + new BigInteger("42") | 42 + new BigDecimal("42") | 42 + 42.0f | 42 + 42.0d | 42 + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567 + new AtomicInteger(42) | 42 + Integer.MAX_VALUE | Integer.MAX_VALUE + Integer.MIN_VALUE | Integer.MIN_VALUE + } + + @Unroll + def "Int serialize #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLInt.getCoercing().serialize(value) == result // Retain deprecated for test coverage + Scalars.GraphQLInt.getCoercing().parseValue(value) == result // Retain deprecated for test coverage where: value | result @@ -73,7 +109,7 @@ class ScalarsIntTest extends Specification { @Unroll def "serialize throws exception for invalid input #value"() { when: - Scalars.GraphQLInt.getCoercing().serialize(value) + Scalars.GraphQLInt.getCoercing().serialize(value, GraphQLContext.default, Locale.default) then: thrown(CoercingSerializeException) @@ -88,17 +124,15 @@ class ScalarsIntTest extends Specification { Integer.MAX_VALUE + 1l | _ Integer.MIN_VALUE - 1l | _ new Object() | _ - } @Unroll - def "parsValue throws exception for invalid input #value"() { + def "parseValue throws exception for invalid input #value"() { when: - Scalars.GraphQLInt.getCoercing().parseValue(value) + Scalars.GraphQLInt.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) then: thrown(CoercingParseValueException) - where: value | _ "" | _ @@ -110,7 +144,6 @@ class ScalarsIntTest extends Specification { Integer.MAX_VALUE + 1l | _ Integer.MIN_VALUE - 1l | _ new Object() | _ - } } diff --git a/src/test/groovy/graphql/ScalarsStringTest.groovy b/src/test/groovy/graphql/ScalarsStringTest.groovy index f25ead1c74..e19e02796a 100644 --- a/src/test/groovy/graphql/ScalarsStringTest.groovy +++ b/src/test/groovy/graphql/ScalarsStringTest.groovy @@ -1,16 +1,15 @@ package graphql +import graphql.execution.CoercedVariables import graphql.language.BooleanValue import graphql.language.StringValue import graphql.schema.CoercingParseLiteralException -import graphql.schema.CoercingParseValueException import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll class ScalarsStringTest extends Specification { - @Shared def customObject = new Object() { @Override @@ -22,18 +21,27 @@ class ScalarsStringTest extends Specification { @Unroll def "String parse literal #literal.value as #result"() { expect: - Scalars.GraphQLString.getCoercing().parseLiteral(literal) == result + Scalars.GraphQLString.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) == result where: literal | result new StringValue("1234ab") | "1234ab" + } + + @Unroll + def "String parse literal #literal.value as #result with deprecated method"() { + expect: + Scalars.GraphQLString.getCoercing().parseLiteral(literal) == result // Retain deprecated for test coverage + where: + literal | result + new StringValue("1234ab") | "1234ab" } @Unroll def "String returns null for invalid #literal"() { when: - Scalars.GraphQLString.getCoercing().parseLiteral(literal) + Scalars.GraphQLString.getCoercing().parseLiteral(literal, CoercedVariables.emptyVariables(), GraphQLContext.default, Locale.default) then: thrown(CoercingParseLiteralException) @@ -45,8 +53,8 @@ class ScalarsStringTest extends Specification { @Unroll def "String serialize #value into #result (#result.class)"() { expect: - Scalars.GraphQLString.getCoercing().serialize(value) == result - Scalars.GraphQLString.getCoercing().parseValue(value) == result + Scalars.GraphQLString.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result + Scalars.GraphQLString.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result where: value | result @@ -55,5 +63,17 @@ class ScalarsStringTest extends Specification { customObject | "foo" } + @Unroll + def "String serialize #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLString.getCoercing().serialize(value) == result // Retain deprecated method for test coverage + Scalars.GraphQLString.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage + + where: + value | result + "123ab" | "123ab" + 123 | "123" + customObject | "foo" + } } diff --git a/src/test/groovy/graphql/TypeResolverExecutionTest.groovy b/src/test/groovy/graphql/TypeResolverExecutionTest.groovy index 4e4ae2f693..b19cf59a15 100644 --- a/src/test/groovy/graphql/TypeResolverExecutionTest.groovy +++ b/src/test/groovy/graphql/TypeResolverExecutionTest.groovy @@ -612,14 +612,12 @@ class TypeResolverExecutionTest extends Specification { GraphQLObjectType getType(TypeResolutionEnvironment env) { assert env.getField().getName() == "foo" assert env.getFieldType() == env.getSchema().getType("BarInterface") - assert env.getContext() == "Context" assert env.getGraphQLContext().get("x") == "graphqlContext" assert env.getLocalContext() == "LocalContext" return env.getSchema().getObjectType("NewBar") } } - def df = { env -> DataFetcherResult.newResult().data([name: "NAME"]).localContext("LocalContext").build() } @@ -648,7 +646,6 @@ class TypeResolverExecutionTest extends Specification { when: def ei = newExecutionInput(query) - .context("Context") .graphQLContext(["x" : "graphqlContext"]) .build() def er = graphQL.execute(ei) diff --git a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy index 336bc382ba..203d12403e 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy @@ -172,9 +172,12 @@ class ValuesResolverTest extends Specification { given: "schema defining input object" def inputObjectType = newInputObject() .name("inputObject") + .field(newInputObjectField() + .name("inputField") + .type(GraphQLString)) .build() - def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValue("hello").build() + def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValueProgrammatic([inputField: "hello"]).build() def argument = new Argument("arg", new VariableReference("var")) when: @@ -182,7 +185,7 @@ class ValuesResolverTest extends Specification { def values = ValuesResolver.getArgumentValues([fieldArgument], [argument], variables, graphQLContext, locale) then: - values['arg'] == 'hello' + values['arg'] == [inputField: 'hello'] } def "getArgumentValues: resolves object literal"() { @@ -532,7 +535,7 @@ class ValuesResolverTest extends Specification { .name("inputObject") .build() - def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValue("hello").build() + def fieldArgument = newArgument().name("arg").type(inputObjectType).defaultValueProgrammatic("hello").build() def argument = new Argument("arg", new VariableReference("var")) when: diff --git a/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy b/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy index ec406d7c42..1c25262b48 100644 --- a/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy +++ b/src/test/groovy/graphql/execution/directives/QueryDirectivesImplTest.groovy @@ -48,10 +48,10 @@ class QueryDirectivesImplTest extends Specification { result[0].getName() == "cached" result[1].getName() == "cached" - result[0].getArgument("forMillis").getArgumentValue().value == 99 // defaults + result[0].getArgument("forMillis").getArgumentValue().value == 99 // defaults. Retain deprecated method to test getImmediateDirective printAst(result[0].getArgument("forMillis").getArgumentDefaultValue().getValue()) == "99" - result[1].getArgument("forMillis").getArgumentValue().value == 10 + result[1].getArgument("forMillis").getArgumentValue().value == 10 // Retain deprecated method to test getImmediateDirective printAst(result[1].getArgument("forMillis").getArgumentDefaultValue().getValue()) == "99" // the prototypical other properties are copied ok diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 5dd073e629..0590ae766e 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -11,6 +11,13 @@ import spock.lang.See import spock.lang.Specification import static graphql.GraphQL.newGraphQL +import static graphql.Scalars.GraphQLString +import static graphql.schema.GraphQLArgument.newArgument +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition +import static graphql.schema.GraphQLInputObjectField.newInputObjectField +import static graphql.schema.GraphQLInputObjectType.newInputObject +import static graphql.schema.GraphQLObjectType.newObject +import static graphql.schema.GraphQLSchema.newSchema class IntrospectionTest extends Specification { @@ -143,7 +150,7 @@ class IntrospectionTest extends Specification { then: executionResult.errors.isEmpty() - def directives = executionResult.data.getAt("__schema").getAt("directives") as List + def directives = executionResult.data["__schema"]["directives"] as List def geoPolygonType = directives.find { it['name'] == 'repeatableDirective' } geoPolygonType["isRepeatable"] == true } @@ -353,4 +360,79 @@ class IntrospectionTest extends Specification { then: queryTypeFields == [[name: "inA"], [name: "inB"], [name: "inC"]] } + + def "test introspection for #296 with map"() { + + def graphql = newGraphQL(newSchema() + .query(newObject() + .name("Query") + .field(newFieldDefinition() + .name("field") + .type(GraphQLString) + .argument(newArgument() + .name("argument") + .type(newInputObject() + .name("InputObjectType") + .field(newInputObjectField() + .name("inputField") + .type(GraphQLString)) + .build()) + .defaultValueProgrammatic([inputField: 'value1']) + ) + ) + ) + .build() + ).build() + + def query = '{ __type(name: "Query") { fields { args { defaultValue } } } }' + + expect: + // converts the default object value to AST, then graphql pretty prints that as the value + graphql.execute(query).data == + [__type: [fields: [[args: [[defaultValue: '{inputField : "value1"}']]]]]] + } + + class FooBar { + final String inputField = "foo" + final String bar = "bar" + + String getInputField() { + return inputField + } + + String getBar() { + return bar + } + } + + def "test introspection for #296 with some object"() { + + def graphql = newGraphQL(newSchema() + .query(newObject() + .name("Query") + .field(newFieldDefinition() + .name("field") + .type(GraphQLString) + .argument(newArgument() + .name("argument") + .type(newInputObject() + .name("InputObjectType") + .field(newInputObjectField() + .name("inputField") + .type(GraphQLString)) + .build()) + .defaultValue(new FooBar()) + ) + ) + ) + .build() + ).build() + + def query = '{ __type(name: "Query") { fields { args { defaultValue } } } }' + + expect: + // converts the default object value to AST, then graphql pretty prints that as the value + graphql.execute(query).data == + [__type: [fields: [[args: [[defaultValue: '{inputField : "foo"}']]]]]] + } } diff --git a/src/test/groovy/graphql/Issue2001.groovy b/src/test/groovy/graphql/schema/GraphQLAppliedDirectiveArgumentTest.groovy similarity index 73% rename from src/test/groovy/graphql/Issue2001.groovy rename to src/test/groovy/graphql/schema/GraphQLAppliedDirectiveArgumentTest.groovy index e18b69be5a..35875c78d2 100644 --- a/src/test/groovy/graphql/Issue2001.groovy +++ b/src/test/groovy/graphql/schema/GraphQLAppliedDirectiveArgumentTest.groovy @@ -1,16 +1,16 @@ -package graphql +package graphql.schema +import graphql.GraphQLContext +import graphql.TestUtil import graphql.execution.ValuesResolver -import graphql.schema.GraphQLArgument import graphql.schema.idl.RuntimeWiring import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.SchemaParser import graphql.schema.idl.errors.SchemaProblem import spock.lang.Specification -class Issue2001 extends Specification { - - def "test non-list value for a list argument of a directive"() { +class GraphQLAppliedDirectiveArgumentTest extends Specification { + def "test non-list value for a list argument of a directive - issue 2001"() { def spec = ''' directive @test(value: [String] = "default") on FIELD_DEFINITION type Query { @@ -21,15 +21,15 @@ class Issue2001 extends Specification { ''' def closure = { - def argument = it.fieldDefinition.getDirective("test").getArgument("value") as GraphQLArgument + def argument = it.fieldDefinition.getAppliedDirective("test").getArgument("value") as GraphQLAppliedDirectiveArgument return ValuesResolver.valueToInternalValue(argument.getArgumentValue(), argument.getType(), GraphQLContext.getDefault(), Locale.getDefault())[0] } def graphql = TestUtil.graphQL(spec, RuntimeWiring.newRuntimeWiring() - .type("Query", { - it.dataFetcher("testDefaultWorks", closure) - .dataFetcher("testItWorks", closure) - .dataFetcher("testItIsNotBroken", closure) - }).build()) + .type("Query", { + it.dataFetcher("testDefaultWorks", closure) + .dataFetcher("testItWorks", closure) + .dataFetcher("testItIsNotBroken", closure) + }).build()) .build() when: @@ -41,7 +41,8 @@ class Issue2001 extends Specification { result.data.testItWorks == "test" result.data.testItIsNotBroken == "test" } - def "test an incorrect non-list value for a list argument of a directive"() { + + def "test an incorrect non-list value for a list argument of a directive - issue 2001"() { def spec = ''' directive @test(value: [String]) on FIELD_DEFINITION type Query { @@ -49,7 +50,6 @@ class Issue2001 extends Specification { } ''' - when: def reader = new StringReader(spec) def registry = new SchemaParser().parse(reader) diff --git a/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy b/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy index 38e3c15388..0e06c3b2b1 100644 --- a/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy @@ -25,9 +25,9 @@ class GraphQLArgumentTest extends Specification { .description("A2_description") .type(GraphQLString) .withDirective(newDirective().name("directive3")) - .value("VALUE") + .value("VALUE") // Retain deprecated for test coverage .deprecate(null) - .defaultValue("DEFAULT") + .defaultValue("DEFAULT") // Retain deprecated for test coverage }) then: @@ -43,7 +43,7 @@ class GraphQLArgumentTest extends Specification { transformedArgument.name == "A2" transformedArgument.description == "A2_description" transformedArgument.type == GraphQLString - transformedArgument.argumentValue.value == "VALUE" + transformedArgument.argumentValue.value == "VALUE" // Retain deprecated for test coverage transformedArgument.argumentDefaultValue.value == "DEFAULT" transformedArgument.deprecationReason == null !transformedArgument.isDeprecated() @@ -116,16 +116,17 @@ class GraphQLArgumentTest extends Specification { } def "can get values statically"() { + // Retain deprecated API usages in this test for test coverage when: GraphQLArgument startingArg = GraphQLArgument.newArgument() .name("F1") .type(GraphQLFloat) .description("F1_description") - .valueProgrammatic(4.56d) + .valueProgrammatic(4.56d) // Retain deprecated for test coverage .defaultValueProgrammatic(1.23d) .build() - def inputValue = startingArg.getArgumentValue() - def resolvedValue = GraphQLArgument.getArgumentValue(startingArg) + def inputValue = startingArg.getArgumentValue() // Retain deprecated for test coverage + def resolvedValue = GraphQLArgument.getArgumentValue(startingArg) // Retain deprecated for test coverage def inputDefaultValue = startingArg.getArgumentDefaultValue() def resolvedDefaultValue = GraphQLArgument.getArgumentDefaultValue(startingArg) @@ -144,12 +145,12 @@ class GraphQLArgumentTest extends Specification { .name("F1") .type(GraphQLFloat) .description("F1_description") - .valueLiteral(FloatValue.newFloatValue().value(4.56d).build()) + .valueLiteral(FloatValue.newFloatValue().value(4.56d).build()) // Retain deprecated for test coverage .defaultValueLiteral(FloatValue.newFloatValue().value(1.23d).build()) .build() - inputValue = startingArg.getArgumentValue() - resolvedValue = GraphQLArgument.getArgumentValue(startingArg) + inputValue = startingArg.getArgumentValue() // Retain deprecated for test coverage + resolvedValue = GraphQLArgument.getArgumentValue(startingArg) // Retain deprecated for test coverage inputDefaultValue = startingArg.getArgumentDefaultValue() resolvedDefaultValue = GraphQLArgument.getArgumentDefaultValue(startingArg) @@ -171,8 +172,8 @@ class GraphQLArgumentTest extends Specification { .description("F1_description") .build() - inputValue = startingArg.getArgumentValue() - resolvedValue = GraphQLArgument.getArgumentValue(startingArg) + inputValue = startingArg.getArgumentValue() // Retain deprecated for test coverage + resolvedValue = GraphQLArgument.getArgumentValue(startingArg) // Retain deprecated for test coverage inputDefaultValue = startingArg.getArgumentDefaultValue() resolvedDefaultValue = GraphQLArgument.getArgumentDefaultValue(startingArg) diff --git a/src/test/groovy/graphql/schema/GraphQLDirectiveTest.groovy b/src/test/groovy/graphql/schema/GraphQLDirectiveTest.groovy index 8d03f982f4..5eba0fbc3a 100644 --- a/src/test/groovy/graphql/schema/GraphQLDirectiveTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLDirectiveTest.groovy @@ -1,6 +1,5 @@ package graphql.schema -import graphql.AssertException import graphql.TestUtil import graphql.language.Node import spock.lang.Specification @@ -95,13 +94,12 @@ class GraphQLDirectiveTest extends Specification { when: def schema = TestUtil.schema(sdl) then: - schema.getSchemaDirective("d1").name == "d1" - schema.getSchemaDirectiveByName().keySet() == ["d1"] as Set + schema.getSchemaAppliedDirective("d1").name == "d1" + schema.getAllSchemaAppliedDirectivesByName().keySet() == ["d1", "dr"] as Set - schema.getAllSchemaDirectivesByName().keySet() == ["d1", "dr"] as Set - schema.getAllSchemaDirectivesByName()["d1"].size() == 1 - schema.getAllSchemaDirectivesByName()["dr"].size() == 2 - schema.getAllSchemaDirectivesByName()["dr"].collect({ printAst(it.getArgument("arg").argumentValue.value) }) == ['"a1"', '"a2"'] + schema.getAllSchemaAppliedDirectivesByName()["d1"].size() == 1 + schema.getAllSchemaAppliedDirectivesByName()["dr"].size() == 2 + schema.getAllSchemaAppliedDirectivesByName()["dr"].collect({ printAst(it.getArgument("arg").argumentValue.value) }) == ['"a1"', '"a2"'] when: def queryType = schema.getObjectType("Query") @@ -174,20 +172,23 @@ class GraphQLDirectiveTest extends Specification { } static boolean assertDirectiveContainer(GraphQLDirectiveContainer container) { - assert container.hasDirective("d1") - assert container.hasDirective("dr") - assert !container.hasDirective("non existent") - assert container.getDirectives().collect({ it.name }) == ["d1", "dr", "dr"] - assert container.getDirective("d1").name == "d1" - assert container.getDirectivesByName().keySet() == ["d1"] as Set - - assert container.getAllDirectivesByName().keySet() == ["d1", "dr"] as Set - assert container.getAllDirectivesByName()["d1"].size() == 1 - assert container.getAllDirectivesByName()["dr"].size() == 2 - - assert container.getDirectives("d1").size() == 1 - assert container.getDirectives("dr").size() == 2 - assert container.getDirectives("dr").collect({ printAst(it.getArgument("arg").argumentValue.value as Node) }) == ['"a1"', '"a2"'] + assert container.hasDirective("d1") // Retain for test coverage + assert container.hasAppliedDirective("d1") + assert container.hasAppliedDirective("dr") + assert !container.hasAppliedDirective("non existent") + assert container.getDirectives().collect({ it.name }) == ["d1", "dr", "dr"] // Retain for test coverage + assert container.getAppliedDirectives().collect({ it.name }) == ["d1", "dr", "dr"] + assert container.getAppliedDirective("d1").name == "d1" + assert container.getDirectivesByName().keySet() == ["d1"] as Set // Retain for test coverage, there is no equivalent non-repeatable directive method + + assert container.getAllDirectivesByName().keySet() == ["d1", "dr"] as Set // Retain for test coverage + assert container.getAllAppliedDirectivesByName().keySet() == ["d1", "dr"] as Set + assert container.getAllAppliedDirectivesByName()["d1"].size() == 1 + assert container.getAllAppliedDirectivesByName()["dr"].size() == 2 + + assert container.getAppliedDirectives("d1").size() == 1 + assert container.getAppliedDirectives("dr").size() == 2 + assert container.getAppliedDirectives("dr").collect({ printAst(it.getArgument("arg").argumentValue.value as Node) }) == ['"a1"', '"a2"'] return true } diff --git a/src/test/groovy/graphql/schema/GraphQLEnumTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLEnumTypeTest.groovy index cb832d323f..d717e607a7 100644 --- a/src/test/groovy/graphql/schema/GraphQLEnumTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLEnumTypeTest.groovy @@ -1,6 +1,7 @@ package graphql.schema import graphql.AssertException +import graphql.GraphQLContext import graphql.language.EnumValue import graphql.language.StringValue import spock.lang.Specification @@ -19,32 +20,52 @@ class GraphQLEnumTypeTest extends Specification { def "parse throws exception for unknown value"() { when: - enumType.parseValue("UNKNOWN") + enumType.parseValue("UNKNOWN", GraphQLContext.default, Locale.default) then: thrown(CoercingParseValueException) } + def "parse throws exception for unknown value with deprecated method"() { + when: + enumType.parseValue("UNKNOWN") // Retain for test coverage + + then: + thrown(CoercingParseValueException) + } def "parse value return value for the name"() { expect: - enumType.parseValue("NAME") == 42 + enumType.parseValue("NAME", GraphQLContext.default, Locale.default) == 42 } def "serialize returns name for value"() { + expect: + enumType.serialize(42, GraphQLContext.default, Locale.default) == "NAME" + } + + def "serialize returns name for value with deprecated method"() { expect: enumType.serialize(42) == "NAME" } def "serialize throws exception for unknown value"() { when: - enumType.serialize(12) + enumType.serialize(12, GraphQLContext.default, Locale.default) then: thrown(CoercingSerializeException) } - def "parseLiteral return null for invalid input"() { + when: + enumType.parseLiteral(StringValue.newStringValue("foo").build(), + GraphQLContext.default, + Locale.default) + then: + thrown(CoercingParseLiteralException) + } + + def "parseLiteral return null for invalid input with deprecated method"() { when: enumType.parseLiteral(StringValue.newStringValue("foo").build()) then: @@ -53,17 +74,20 @@ class GraphQLEnumTypeTest extends Specification { def "parseLiteral return null for invalid enum name"() { when: - enumType.parseLiteral(EnumValue.newEnumValue("NOT_NAME").build()) + enumType.parseLiteral(EnumValue.newEnumValue("NOT_NAME").build(), + GraphQLContext.default, + Locale.default) then: thrown(CoercingParseLiteralException) } def "parseLiteral returns value for 'NAME'"() { expect: - enumType.parseLiteral(EnumValue.newEnumValue("NAME").build()) == 42 + enumType.parseLiteral(EnumValue.newEnumValue("NAME").build(), + GraphQLContext.default, + Locale.default) == 42 } - def "null values are not allowed"() { when: newEnum().name("AnotherTestEnum") @@ -72,7 +96,6 @@ class GraphQLEnumTypeTest extends Specification { thrown(AssertException) } - def "duplicate value definition overwrites"() { when: def enumType = newEnum().name("AnotherTestEnum") @@ -96,7 +119,7 @@ class GraphQLEnumTypeTest extends Specification { .build() when: - def serialized = enumType.serialize(Episode.EMPIRE) + def serialized = enumType.serialize(Episode.EMPIRE, GraphQLContext.default, Locale.default) then: serialized == "EMPIRE" @@ -111,7 +134,7 @@ class GraphQLEnumTypeTest extends Specification { .build() when: - def serialized = enumType.serialize(Episode.NEWHOPE) + def serialized = enumType.serialize(Episode.NEWHOPE, GraphQLContext.default, Locale.default) then: serialized == "NEWHOPE" @@ -128,7 +151,7 @@ class GraphQLEnumTypeTest extends Specification { String stringInput = Episode.NEWHOPE.toString() when: - def serialized = enumType.serialize(stringInput) + def serialized = enumType.serialize(stringInput, GraphQLContext.default, Locale.default) then: serialized == "NEWHOPE" diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy index 49da26d07c..0214a6a86f 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy @@ -2,7 +2,6 @@ package graphql.schema.idl import graphql.TestUtil -import graphql.schema.GraphQLArgument import graphql.schema.GraphQLInputType import spock.lang.Specification @@ -69,41 +68,22 @@ class SchemaGeneratorAppliedDirectiveHelperTest extends Specification { barType.directives.collect { it.name }.sort() == ["foo"] barType.appliedDirectives.collect { it.name }.sort() == ["foo"] - - def fooDirective = field.getDirective("foo") - fooDirective.arguments.collect { it.name }.sort() == ["arg1", "arg2"] - fooDirective.arguments.collect { GraphQLArgument.getArgumentValue(it) }.sort() == ["arg2Value", "fooArg1Value",] - def fooAppliedDirective = field.getAppliedDirective("foo") fooAppliedDirective.arguments.collect { it.name }.sort() == ["arg1", "arg2"] fooAppliedDirective.arguments.collect { it.getValue() }.sort() == ["arg2Value", "fooArg1Value"] - - def fooDirectiveOnType = barType.getDirective("foo") - fooDirectiveOnType.arguments.collect { it.name }.sort() == ["arg1", "arg2"] - fooDirectiveOnType.arguments.collect { GraphQLArgument.getArgumentValue(it) }.sort() == ["BarTypeValue", "arg2Value",] - def fooAppliedDirectiveOnType = barType.getAppliedDirective("foo") fooAppliedDirectiveOnType.arguments.collect { it.name }.sort() == ["arg1", "arg2"] fooAppliedDirectiveOnType.arguments.collect { it.getValue() }.sort() == ["BarTypeValue", "arg2Value",] - - def complexDirective = complexField.getDirective("complex") - complexDirective.arguments.collect { it.name }.sort() == ["complexArg1"] - complexDirective.arguments.collect { GraphQLArgument.getArgumentValue(it) }.sort() == [ - [name:"Boris", address:[number:10, street:"Downing St", town:"London"]] - ] - def complexAppliedDirective = complexField.getAppliedDirective("complex") GraphQLInputType complexInputType = schema.getTypeAs("ComplexInput") complexAppliedDirective.arguments.collect { it.name }.sort() == ["complexArg1"] complexAppliedDirective.arguments.collect { it.getValue() }.sort() == [ [name:"Boris", address:[number:10, street:"Downing St", town:"London"]] ] - } - def "can capture ONLY applied directives"() { when: diff --git a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy index e763932de6..513c214445 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy @@ -5,9 +5,11 @@ import graphql.TestUtil import graphql.TypeResolutionEnvironment import graphql.introspection.IntrospectionQuery import graphql.introspection.IntrospectionResultToSchema +import graphql.language.IntValue +import graphql.language.StringValue import graphql.schema.Coercing +import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLCodeRegistry -import graphql.schema.GraphQLDirective import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLEnumValueDefinition import graphql.schema.GraphQLFieldDefinition @@ -99,9 +101,20 @@ class SchemaPrinterTest extends Specification { } def "argsString"() { - def argument1 = newArgument().name("arg1").type(list(nonNull(GraphQLInt))).defaultValue(10).build() - def argument2 = newArgument().name("arg2").type(GraphQLString).build(); - def argument3 = newArgument().name("arg3").type(GraphQLString).defaultValue("default").build() + def argument1 = newArgument() + .name("arg1") + .type(list(nonNull(GraphQLInt))) + .defaultValueLiteral(IntValue.newIntValue().value(10).build()) + .build() + def argument2 = newArgument() + .name("arg2") + .type(GraphQLString) + .build() + def argument3 = newArgument() + .name("arg3") + .type(GraphQLString) + .defaultValueLiteral(StringValue.newStringValue().value("default").build()) + .build() def argStr = new SchemaPrinter().argsString([argument1, argument2, argument3]) expect: @@ -110,9 +123,20 @@ class SchemaPrinterTest extends Specification { } def "argsString_sorts"() { - def argument1 = newArgument().name("arg1").type(list(nonNull(GraphQLInt))).defaultValue(10).build() - def argument2 = newArgument().name("arg2").type(GraphQLString).build(); - def argument3 = newArgument().name("arg3").type(GraphQLString).defaultValue("default").build() + def argument1 = newArgument() + .name("arg1") + .type(list(nonNull(GraphQLInt))) + .defaultValueLiteral(IntValue.newIntValue().value(10).build()) + .build() + def argument2 = newArgument() + .name("arg2") + .type(GraphQLString) + .build() + def argument3 = newArgument() + .name("arg3") + .type(GraphQLString) + .defaultValueLiteral(StringValue.newStringValue().value("default").build()) + .build() def argStr = new SchemaPrinter().argsString([argument2, argument1, argument3]) expect: @@ -121,8 +145,18 @@ class SchemaPrinterTest extends Specification { } def "argsString_comments"() { - def argument1 = newArgument().name("arg1").description("A multiline\ncomment").type(list(nonNull(GraphQLInt))).defaultValue(10).build() - def argument2 = newArgument().name("arg2").description("A single line comment").type(list(nonNull(GraphQLInt))).defaultValue(10).build() + def argument1 = newArgument() + .name("arg1") + .description("A multiline\ncomment") + .type(list(nonNull(GraphQLInt))) + .defaultValueLiteral(IntValue.newIntValue().value(10).build()) + .build() + def argument2 = newArgument() + .name("arg2") + .description("A single line comment") + .type(list(nonNull(GraphQLInt))) + .defaultValueLiteral(IntValue.newIntValue().value(10).build()) + .build() def argStr = new SchemaPrinter().argsString([argument1, argument2]) expect: @@ -1992,14 +2026,14 @@ type Query { ''' when: - def newDirective = GraphQLDirective.newDirective().name("foo") + def newAppliedDirective = GraphQLAppliedDirective.newDirective().name("foo") .argument({ it.name("arg").type(compoundType).valueProgrammatic(["a": "A", "b": "B"]) }) .build() objType = newObject().name("obj").field({ - it.name("f").type(GraphQLString).withDirective(newDirective) + it.name("f").type(GraphQLString).withAppliedDirective(newAppliedDirective) }).build() result = new SchemaPrinter().print(objType) @@ -2013,17 +2047,17 @@ type Query { } def "directive containing formatting specifiers"() { - def constraintDirective = GraphQLDirective.newDirective().name("constraint") + def constraintAppliedDirective = GraphQLAppliedDirective.newDirective().name("constraint") .argument({ it.name("regex").type(GraphQLString).valueProgrammatic("%") }) .build() + GraphQLInputObjectType type = GraphQLInputObjectType.newInputObject().name("Person") - .field({ it.name("thisMustBeAPercentageSign").type(GraphQLString).withDirective(constraintDirective) }) + .field({ it.name("thisMustBeAPercentageSign").type(GraphQLString).withAppliedDirective(constraintAppliedDirective) }) .build() when: - def result = new SchemaPrinter().print(type) diff --git a/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy b/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy index d0d61fa4ae..bf134d063c 100644 --- a/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy +++ b/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy @@ -39,7 +39,7 @@ class TypesImplementInterfacesTest extends Specification { .argument(newArgument().name("arg1").type(GraphQLString)) .argument(newArgument().name("arg2").type(GraphQLInt)) .argument(newArgument().name("arg3").type(GraphQLBoolean)) - .argument(newArgument().name("arg4").type(GraphQLString).defaultValue("ABC")) + .argument(newArgument().name("arg4").type(GraphQLString).defaultValueProgrammatic("ABC")) ) .field(newFieldDefinition().name("argField2").type(GraphQLString) @@ -54,7 +54,7 @@ class TypesImplementInterfacesTest extends Specification { given: SchemaValidationErrorCollector errorCollector = new SchemaValidationErrorCollector() - GraphQLObjectType objType = GraphQLObjectType.newObject() + GraphQLObjectType objType = newObject() .name("obj") .withInterface(InterfaceType) .field(newFieldDefinition().name("name").type(GraphQLString)) @@ -66,7 +66,7 @@ class TypesImplementInterfacesTest extends Specification { .argument(newArgument().name("arg1").type(GraphQLInt)) .argument(newArgument().name("arg2").type(GraphQLInt)) .argument(newArgument().name("arg3").type(GraphQLInt)) - .argument(newArgument().name("arg4").type(GraphQLString).defaultValue("XYZ")) + .argument(newArgument().name("arg4").type(GraphQLString).defaultValueProgrammatic("XYZ")) ) .field(newFieldDefinition().name("argField2").type(GraphQLString) @@ -376,7 +376,7 @@ class TypesImplementInterfacesTest extends Specification { SchemaValidationErrorCollector errorCollector = new SchemaValidationErrorCollector() - GraphQLObjectType objType = GraphQLObjectType.newObject() + GraphQLObjectType objType = newObject() .name("Object") .withInterface(InterfaceType) .field(newFieldDefinition().name("argField").type(GraphQLString) @@ -405,7 +405,7 @@ class TypesImplementInterfacesTest extends Specification { SchemaValidationErrorCollector errorCollector = new SchemaValidationErrorCollector() - GraphQLObjectType objType = GraphQLObjectType.newObject() + GraphQLObjectType objType = newObject() .name("Object") .withInterface(InterfaceType) .field(newFieldDefinition().name("argField").type(GraphQLString) @@ -433,7 +433,7 @@ class TypesImplementInterfacesTest extends Specification { SchemaValidationErrorCollector errorCollector = new SchemaValidationErrorCollector() - GraphQLObjectType objType = GraphQLObjectType.newObject() + GraphQLObjectType objType = newObject() .name("Object") .withInterface(InterfaceType) .field(newFieldDefinition().name("argField").type(GraphQLString) @@ -461,7 +461,7 @@ class TypesImplementInterfacesTest extends Specification { SchemaValidationErrorCollector errorCollector = new SchemaValidationErrorCollector() - GraphQLObjectType objType = GraphQLObjectType.newObject() + GraphQLObjectType objType = newObject() .name("Object") .withInterface(InterfaceType) .field(newFieldDefinition().name("argField2").type(GraphQLString)) diff --git a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy index 8de2808b66..925f6603eb 100644 --- a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy @@ -68,7 +68,7 @@ class ProvidedNonNullArgumentsTest extends Specification { given: def fieldArg = GraphQLArgument.newArgument().name("arg") .type(GraphQLNonNull.nonNull(GraphQLString)) - .defaultValue("defaultVal") + .defaultValueProgrammatic("defaultVal") def fieldDef = GraphQLFieldDefinition.newFieldDefinition() .name("field") .type(GraphQLString) @@ -146,7 +146,7 @@ class ProvidedNonNullArgumentsTest extends Specification { given: def directiveArg = GraphQLArgument.newArgument() .name("arg").type(GraphQLNonNull.nonNull(GraphQLString)) - .defaultValue("defaultVal") + .defaultValueProgrammatic("defaultVal") def graphQLDirective = GraphQLDirective.newDirective() .name("directive") .argument(directiveArg) From a32eb24b69d3a3e02b28972c55ed631dd7919de2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Sep 2022 20:24:22 +1000 Subject: [PATCH 119/294] Fix field definition data fetcher tests --- .../DataFetcherWithErrorsAndDataTest.groovy | 150 +++++++---- .../groovy/graphql/NonNullHandlingTest.groovy | 243 ++++++++++++------ .../AsyncExecutionStrategyTest.groovy | 36 ++- .../AsyncSerialExecutionStrategyTest.groovy | 41 ++- .../execution/ExecutionStrategyTest.groovy | 89 +++++-- .../dataloader/DataLoaderNodeTest.groovy | 65 +++-- .../schema/GraphQLCodeRegistryTest.groovy | 2 +- .../schema/GraphQLFieldDefinitionTest.groovy | 2 +- .../graphql/schema/GraphQLSchemaTest.groovy | 75 ++++-- .../schema/SchemaTransformerTest.groovy | 69 +++-- .../GraphqlFieldVisibilityTest.groovy | 29 ++- 11 files changed, 545 insertions(+), 256 deletions(-) diff --git a/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy b/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy index 7b758466b5..2d5e8efa02 100644 --- a/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy +++ b/src/test/groovy/graphql/DataFetcherWithErrorsAndDataTest.groovy @@ -3,6 +3,10 @@ package graphql import graphql.execution.AsyncExecutionStrategy import graphql.execution.AsyncSerialExecutionStrategy import graphql.language.SourceLocation +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLOutputType import graphql.schema.GraphQLSchema import graphql.schema.idl.RuntimeWiring @@ -32,55 +36,91 @@ class DataFetcherWithErrorsAndDataTest extends Specification { ChildObject child = new ChildObject() } - def executionInput(String query) { + static def executionInput(String query) { newExecutionInput().query(query).build() } + class ParentDataFetcher implements DataFetcher { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return newResult() + .data(new ParentObject()) + .errors([newError() + .message("badField is bad") + .path(["root", "parent", "child", "badField"]) + .location(environment.getField().getSourceLocation()) + .build()]) + .build() + } + } + + class ChildDataFetcher implements DataFetcher { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return newResult() + .data(["goodField": null, "badField": null]) + .errors([newError() + .message("goodField is bad") + .path(["root", "parent", "child", "goodField"]) + .location(environment.getField().getSourceLocation()) + .build(), + newError().message("badField is bad") + .path(["root", "parent", "child", "badField"]) + .location(environment.getField().getSourceLocation()) + .build()]) + .build() + } + } + @Unroll def "#820 - data fetcher can return data and errors (strategy: #strategyName)"() { - // see https://github.com/graphql-java/graphql-java/issues/820 given: - + def queryTypeName = "QueryType" + def rootFieldName = "root" + def rootTypeName = "rootType" + def parentFieldName = "parent" GraphQLOutputType childType = newObject() .name("childType") - .field(newFieldDefinition().name("goodField") + .field(newFieldDefinition() + .name("goodField") .type(GraphQLString)) - .field(newFieldDefinition().name("badField") + .field(newFieldDefinition() + .name("badField") .type(GraphQLString)) .build() GraphQLOutputType parentType = newObject() .name("parentType") - .field(newFieldDefinition().name("child") + .field(newFieldDefinition() + .name("child") .type(childType)) .build() GraphQLOutputType rootType = newObject() - .name("rootType") - .field(newFieldDefinition().name("parent") - .type(parentType) - .dataFetcher({ env -> - newResult() - .data(new ParentObject()) - .errors([newError() - .message("badField is bad") - .path(["root", "parent", "child", "badField"]) - .location(env.getField().getSourceLocation()) - .build()]) - .build() - - })) + .name(rootTypeName) + .field(newFieldDefinition() + .name(parentFieldName) + .type(parentType)) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") + def rootTypeCoordinates = FieldCoordinates.coordinates(queryTypeName, rootFieldName) + def parentTypeCoordinates = FieldCoordinates.coordinates(rootTypeName, parentFieldName) + DataFetcher rootTypeDataFetcher = { env -> [:] } + DataFetcher parentTypeDataFetcher = new ParentDataFetcher() + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(rootTypeCoordinates, rootTypeDataFetcher) + .dataFetcher(parentTypeCoordinates, parentTypeDataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryTypeName) .field(newFieldDefinition() - .name("root") + .name(rootFieldName) .type(rootType) - .dataFetcher({ env -> [:] }) - )) .build() @@ -122,52 +162,53 @@ class DataFetcherWithErrorsAndDataTest extends Specification { @Unroll def "#820 - data fetcher can return multiple errors (strategy: #strategyName)"() { - // see https://github.com/graphql-java/graphql-java/issues/820 given: - + def queryTypeName = "QueryType" + def rootFieldName = "root" + def parentTypeName = "parentType" + def childFieldName = "child" GraphQLOutputType childType = newObject() .name("childType") - .field(newFieldDefinition().name("goodField") + .field(newFieldDefinition() + .name("goodField") .type(GraphQLString)) - .field(newFieldDefinition().name("badField") + .field(newFieldDefinition() + .name("badField") .type(GraphQLString)) .build() GraphQLOutputType parentType = newObject() - .name("parentType") - .field(newFieldDefinition().name("child") - .type(childType) - .dataFetcher({ env -> - newResult() - .data(["goodField": null, "badField": null]) - .errors([ - newError().message("goodField is bad") - .path(["root", "parent", "child", "goodField"]) - .location(env.getField().getSourceLocation()) - .build(), - newError().message("badField is bad") - .path(["root", "parent", "child", "badField"]) - .location(env.getField().getSourceLocation()) - .build() - ]).build() - })) + .name(parentTypeName) + .field(newFieldDefinition() + .name(childFieldName) + .type(childType)) .build() GraphQLOutputType rootType = newObject() .name("rootType") - .field(newFieldDefinition().name("parent") + .field(newFieldDefinition() + .name("parent") .type(parentType)) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") + def rootTypeCoordinates = FieldCoordinates.coordinates(queryTypeName, rootFieldName) + def childTypeCoordinates = FieldCoordinates.coordinates(parentTypeName, childFieldName) + DataFetcher rootTypeDataFetcher = { env -> ["parent": [:]] } + DataFetcher childTypeDataFetcher = new ChildDataFetcher() + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(rootTypeCoordinates, rootTypeDataFetcher) + .dataFetcher(childTypeCoordinates, childTypeDataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryTypeName) .field(newFieldDefinition() - .name("root") + .name(rootFieldName) .type(rootType) - .dataFetcher({ env -> ["parent": [:]] }) - )) .build() @@ -210,7 +251,6 @@ class DataFetcherWithErrorsAndDataTest extends Specification { 'asyncSerial' | new AsyncSerialExecutionStrategy() } - @Unroll def "data fetcher can return context down each level (strategy: #strategyName)"() { given: diff --git a/src/test/groovy/graphql/NonNullHandlingTest.groovy b/src/test/groovy/graphql/NonNullHandlingTest.groovy index b08b8759a6..a6a352ef93 100644 --- a/src/test/groovy/graphql/NonNullHandlingTest.groovy +++ b/src/test/groovy/graphql/NonNullHandlingTest.groovy @@ -2,6 +2,10 @@ package graphql import graphql.execution.AsyncExecutionStrategy import graphql.execution.AsyncSerialExecutionStrategy +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLOutputType import graphql.schema.GraphQLSchema import spock.lang.Specification @@ -32,36 +36,47 @@ class NonNullHandlingTest extends Specification { SimpleObject nonNullParent = new SimpleObject() } - def executionInput(String query) { + static def executionInput(String query) { ExecutionInput.newExecutionInput().query(query).build() } @Unroll def "#268 - null child field values are allowed in nullable parent type (strategy: #strategyName)"() { - // see https://github.com/graphql-java/graphql-java/issues/268 given: - - + def rootTypeName = "RootQueryType" + def parentFieldName = "parent" GraphQLOutputType parentType = newObject() .name("currentType") - .field(newFieldDefinition().name("nullChild") + .field(newFieldDefinition() + .name("nullChild") .type(nonNull(GraphQLString))) - .field(newFieldDefinition().name("nonNullChild") + .field(newFieldDefinition() + .name("nonNullChild") .type(nonNull(GraphQLString))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") + def parentCoordinates = FieldCoordinates.coordinates(rootTypeName, parentFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new SimpleObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(parentCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) .field(newFieldDefinition() - .name("parent") + .name(parentFieldName) .type(parentType) // nullable parent - .dataFetcher({ env -> new SimpleObject() }) - - )) - .build() + ) + ).build() def query = """ query { @@ -92,11 +107,11 @@ class NonNullHandlingTest extends Specification { @Unroll def "#268 - null child field values are NOT allowed in non nullable parent types (strategy: #strategyName)"() { - // see https://github.com/graphql-java/graphql-java/issues/268 given: - + def rootTypeName = "RootQueryType" + def parentFieldName = "parent" GraphQLOutputType parentType = newObject() .name("currentType") @@ -106,17 +121,27 @@ class NonNullHandlingTest extends Specification { .type(nonNull(GraphQLString))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") + def parentCoordinates = FieldCoordinates.coordinates(rootTypeName, parentFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new SimpleObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(parentCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) .field( newFieldDefinition() - .name("parent") + .name(parentFieldName) .type(nonNull(parentType)) // non nullable parent - .dataFetcher({ env -> new SimpleObject() }) - - )) - .build() + ) + ).build() def query = """ query { @@ -148,38 +173,53 @@ class NonNullHandlingTest extends Specification { @Unroll def "#581 - null child field values are allowed in nullable grand parent type (strategy: #strategyName)"() { - given: + def rootTypeName = "RootQueryType" + def topFieldName = "top" GraphQLOutputType parentType = newObject() .name("parentType") - .field(newFieldDefinition().name("nullChild") + .field(newFieldDefinition() + .name("nullChild") .type(nonNull(GraphQLString))) - .field(newFieldDefinition().name("nonNullChild") + .field(newFieldDefinition() + .name("nonNullChild") .type(nonNull(GraphQLString))) .build() GraphQLOutputType topType = newObject() .name("topType") - .field(newFieldDefinition().name("nullParent") + .field(newFieldDefinition() + .name("nullParent") .type(nonNull(parentType))) - .field(newFieldDefinition().name("nonNullParent") + .field(newFieldDefinition() + .name("nonNullParent") .type(nonNull(parentType))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field( - newFieldDefinition() - .name("top") - .type(topType) // nullable grand parent - .dataFetcher({ env -> new ContainingObject() }) - - )) + def topCoordinates = FieldCoordinates.coordinates(rootTypeName, topFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new ContainingObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(topCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) + .field(newFieldDefinition() + .name(topFieldName) + .type(topType) // nullable grand parent + + ) + ).build() + def query = """ query { top { @@ -215,36 +255,50 @@ class NonNullHandlingTest extends Specification { def "#581 - null child field values are NOT allowed in non nullable grand parent types (strategy: #strategyName)"() { given: - + def rootTypeName = "RootQueryType" + def topFieldName = "top" GraphQLOutputType parentType = newObject() .name("parentType") - .field(newFieldDefinition().name("nullChild") + .field(newFieldDefinition() + .name("nullChild") .type(nonNull(GraphQLString))) - .field(newFieldDefinition().name("nonNullChild") + .field(newFieldDefinition() + .name("nonNullChild") .type(nonNull(GraphQLString))) .build() GraphQLOutputType topType = newObject() .name("topType") - .field(newFieldDefinition().name("nullParent") + .field(newFieldDefinition() + .name("nullParent") .type(nonNull(parentType))) - .field(newFieldDefinition().name("nonNullParent") + .field(newFieldDefinition() + .name("nonNullParent") .type(nonNull(parentType))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field( - newFieldDefinition() - .name("top") - .type(nonNull(topType)) // non nullable grand parent - .dataFetcher({ env -> new ContainingObject() }) - - )) + def topCoordinates = FieldCoordinates.coordinates(rootTypeName, topFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new ContainingObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(topCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) + .field(newFieldDefinition() + .name(topFieldName) + .type(nonNull(topType)) // non nullable grand parent + ) + ).build() + def query = """ query { top { @@ -280,34 +334,47 @@ class NonNullHandlingTest extends Specification { def "#561 - null entry in non null list type with non null wrapper list (strategy: #strategyName)"() { given: - + def rootTypeName = "RootQueryType" + def topFieldName = "top" GraphQLOutputType parentType = newObject() .name("parentType") - .field(newFieldDefinition().name("nonNullListWithNull") + .field(newFieldDefinition() + .name("nonNullListWithNull") .type(nonNull(list(nonNull(GraphQLString))))) .build() GraphQLOutputType topType = newObject() .name("topType") - .field(newFieldDefinition().name("nullParent") + .field(newFieldDefinition() + .name("nullParent") .type(nonNull(parentType))) - .field(newFieldDefinition().name("nonNullParent") + .field(newFieldDefinition() + .name("nonNullParent") .type(nonNull(parentType))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field( - newFieldDefinition() - .name("top") - .type(nonNull(topType)) // non nullable grand parent - .dataFetcher({ env -> new ContainingObject() }) - - )) + def topCoordinates = FieldCoordinates.coordinates(rootTypeName, topFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new ContainingObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(topCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) + .field(newFieldDefinition() + .name(topFieldName) + .type(nonNull(topType)) // non nullable grand parent + ) + ).build() + def query = """ query { top { @@ -344,33 +411,47 @@ class NonNullHandlingTest extends Specification { given: + def rootTypeName = "RootQueryType" + def topFieldName = "top" GraphQLOutputType parentType = newObject() .name("parentType") - .field(newFieldDefinition().name("nullableListWithNull") + .field(newFieldDefinition() + .name("nullableListWithNull") .type(list(nonNull(GraphQLString)))) .build() GraphQLOutputType topType = newObject() .name("topType") - .field(newFieldDefinition().name("nullParent") + .field(newFieldDefinition() + .name("nullParent") .type(nonNull(parentType))) - .field(newFieldDefinition().name("nonNullParent") + .field(newFieldDefinition() + .name("nonNullParent") .type(nonNull(parentType))) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field( - newFieldDefinition() - .name("top") - .type(nonNull(topType)) // non nullable grand parent - .dataFetcher({ env -> new ContainingObject() }) - - )) + def topCoordinates = FieldCoordinates.coordinates(rootTypeName, topFieldName) + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return new ContainingObject() + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(topCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(rootTypeName) + .field(newFieldDefinition() + .name(topFieldName) + .type(nonNull(topType)) // non nullable grand parent + ) + ).build() + def query = """ query { top { diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 2a082b20c8..90f9afc2ba 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -10,6 +10,8 @@ import graphql.language.Field import graphql.language.OperationDefinition import graphql.parser.Parser import graphql.schema.DataFetcher +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema import spock.lang.Specification @@ -30,26 +32,38 @@ import static org.awaitility.Awaitility.await class AsyncExecutionStrategyTest extends Specification { GraphQLSchema schema(DataFetcher dataFetcher1, DataFetcher dataFetcher2) { - GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() - .name("hello") + def queryName = "RootQueryType" + def field1Name = "hello" + def field2Name = "hello2" + + GraphQLFieldDefinition.Builder fieldDefinition1 = newFieldDefinition() + .name(field1Name) .type(GraphQLString) - .dataFetcher(dataFetcher1) GraphQLFieldDefinition.Builder fieldDefinition2 = newFieldDefinition() - .name("hello2") + .name(field2Name) .type(GraphQLString) - .dataFetcher(dataFetcher2) - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field(fieldDefinition) + def field1Coordinates = FieldCoordinates.coordinates(queryName, field1Name) + def field2Coordinates = FieldCoordinates.coordinates(queryName, field2Name) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(field1Coordinates, dataFetcher1) + .dataFetcher(field2Coordinates, dataFetcher2) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryName) + .field(fieldDefinition1) .field(fieldDefinition2) .build() - ).build() + ) + .build() + schema } - def "execution is serial if the dataFetchers are blocking"() { given: def lock = new ReentrantLock() diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index 9cccbd5552..1b844afb56 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -6,6 +6,8 @@ import graphql.language.Field import graphql.language.OperationDefinition import graphql.parser.Parser import graphql.schema.DataFetcher +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema import spock.lang.Specification @@ -23,27 +25,42 @@ import static graphql.schema.GraphQLSchema.newSchema class AsyncSerialExecutionStrategyTest extends Specification { GraphQLSchema schema(DataFetcher dataFetcher1, DataFetcher dataFetcher2, DataFetcher dataFetcher3) { - GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() - .name("hello") + def queryName = "RootQueryType" + def field1Name = "hello" + def field2Name = "hello2" + def field3Name = "hello3" + + GraphQLFieldDefinition.Builder fieldDefinition1 = newFieldDefinition() + .name(field1Name) .type(GraphQLString) - .dataFetcher(dataFetcher1) GraphQLFieldDefinition.Builder fieldDefinition2 = newFieldDefinition() - .name("hello2") + .name(field2Name) .type(GraphQLString) - .dataFetcher(dataFetcher2) GraphQLFieldDefinition.Builder fieldDefinition3 = newFieldDefinition() - .name("hello3") + .name(field3Name) .type(GraphQLString) - .dataFetcher(dataFetcher3) - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field(fieldDefinition) + def field1Coordinates = FieldCoordinates.coordinates(queryName, field1Name) + def field2Coordinates = FieldCoordinates.coordinates(queryName, field2Name) + def field3Coordinates = FieldCoordinates.coordinates(queryName, field3Name) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(field1Coordinates, dataFetcher1) + .dataFetcher(field2Coordinates, dataFetcher2) + .dataFetcher(field3Coordinates, dataFetcher3) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryName) + .field(fieldDefinition1) .field(fieldDefinition2) .field(fieldDefinition3) .build() - ).build() + ) + .build() + schema } diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index adc5cd57a9..0381ea9c07 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -21,6 +21,8 @@ import graphql.parser.Parser import graphql.schema.Coercing import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLScalarType @@ -86,20 +88,31 @@ class ExecutionStrategyTest extends Specification { given: def dataFetcher = Mock(DataFetcher) + def someFieldName = "someField" + def testTypeName = "Test" def fieldDefinition = newFieldDefinition() - .name("someField") + .name(someFieldName) .type(GraphQLString) - .dataFetcher(dataFetcher) .build() def objectType = newObject() - .name("Test") + .name(testTypeName) .field(fieldDefinition) .build() + def someFieldCoordinates = FieldCoordinates.coordinates(testTypeName, someFieldName) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(someFieldCoordinates, dataFetcher) + .build() + def document = new Parser().parseDocument("{someField}") def operation = document.definitions[0] as OperationDefinition - GraphQLSchema schema = GraphQLSchema.newSchema().query(objectType).build() + GraphQLSchema schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(objectType) + .build() + def builder = new ExecutionContextBuilder() builder.queryStrategy(Mock(ExecutionStrategy)) builder.mutationStrategy(Mock(ExecutionStrategy)) @@ -484,23 +497,33 @@ class ExecutionStrategyTest extends Specification { @SuppressWarnings("GroovyVariableNotAssigned") def "resolveField creates correct DataFetchingEnvironment"() { def dataFetcher = Mock(DataFetcher) + def someFieldName = "someField" + def testTypeName = "Type" def fieldDefinition = newFieldDefinition() - .name("someField") + .name(someFieldName) .type(GraphQLString) - .dataFetcher(dataFetcher) .argument(newArgument().name("arg1").type(GraphQLString)) .build() def objectType = newObject() - .name("Test") + .name(testTypeName) .field(fieldDefinition) .build() - GraphQLSchema schema = GraphQLSchema.newSchema().query(objectType).build() + def someFieldCoordinates = FieldCoordinates.coordinates(testTypeName, someFieldName) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(someFieldCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(objectType) + .build() ExecutionContext executionContext = buildContext(schema) ExecutionStepInfo typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) Argument argument = new Argument("arg1", new StringValue("argVal")) - Field field = new Field("someField", [argument]) + Field field = new Field(someFieldName, [argument]) MergedField mergedField = mergedField(field) ResultPath resultPath = ResultPath.rootPath().segment("test") @@ -540,19 +563,34 @@ class ExecutionStrategyTest extends Specification { throw expectedException } } - def fieldDefinition = newFieldDefinition().name("someField").type(GraphQLString).dataFetcher(dataFetcher).build() + + def someFieldName = "someField" + def testTypeName = "Test" + def fieldDefinition = newFieldDefinition() + .name(someFieldName) + .type(GraphQLString) + .build() def objectType = newObject() - .name("Test") + .name(testTypeName) .field(fieldDefinition) .build() - def schema = GraphQLSchema.newSchema().query(objectType).build() + + def someFieldCoordinates = FieldCoordinates.coordinates(testTypeName, someFieldName) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(someFieldCoordinates, dataFetcher) + .build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(objectType) + .build() ExecutionContext executionContext = buildContext(schema) def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) - ResultPath expectedPath = ResultPath.rootPath().segment("someField") + ResultPath expectedPath = ResultPath.rootPath().segment(someFieldName) SourceLocation sourceLocation = new SourceLocation(666, 999) - Field field = Field.newField("someField").sourceLocation(sourceLocation).build() + Field field = Field.newField(someFieldName).sourceLocation(sourceLocation).build() def parameters = newParameters() .executionStepInfo(typeInfo) .source("source") @@ -564,7 +602,6 @@ class ExecutionStrategyTest extends Specification { [executionContext, fieldDefinition, expectedPath, parameters, field, sourceLocation] } - def "test that the new data fetcher error handler interface is called"() { def expectedException = new UnsupportedOperationException("This is the exception you are looking for") @@ -681,22 +718,34 @@ class ExecutionStrategyTest extends Specification { throw new RuntimeException("bang") } } + + def someFieldName = "someField" + def testTypeName = "Test" + def fieldDefinition = newFieldDefinition() - .name("someField") + .name(someFieldName) .type(nonNull(GraphQLString)) - .dataFetcher(dataFetcher) .build() def objectType = newObject() - .name("Test") + .name(testTypeName) .field(fieldDefinition) .build() - GraphQLSchema schema = GraphQLSchema.newSchema().query(objectType).build() + def someFieldCoordinates = FieldCoordinates.coordinates(testTypeName, someFieldName) + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(someFieldCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(objectType) + .build() ExecutionContext executionContext = buildContext(schema) def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) - Field field = new Field("someField") + Field field = new Field(someFieldName) def parameters = newParameters() .executionStepInfo(typeInfo) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy index 277b4f5d7c..0bfab06b4f 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy @@ -3,6 +3,10 @@ package graphql.execution.instrumentation.dataloader import graphql.ExecutionInput import graphql.ExecutionResult import graphql.GraphQL +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLSchema import graphql.schema.StaticDataFetcher @@ -64,6 +68,19 @@ class DataLoaderNodeTest extends Specification { } + class NodeDataFetcher implements DataFetcher { + DataLoader loader + + NodeDataFetcher(DataLoader loader) { + this.loader = loader + } + + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return loader.load(environment.getSource()) + } + } + def "levels of loading"() { List> nodeLoads = [] @@ -78,31 +95,44 @@ class DataLoaderNodeTest extends Specification { return CompletableFuture.completedFuture(childNodes) }) - GraphQLObjectType nodeType = GraphQLObjectType.newObject() - .name("Node") + DataFetcher nodeDataFetcher = new NodeDataFetcher(loader) + + def nodeTypeName = "Node" + def childNodesFieldName = "childNodes" + def queryTypeName = "Query" + def rootFieldName = "root" + + GraphQLObjectType nodeType = GraphQLObjectType + .newObject() + .name(nodeTypeName) .field(newFieldDefinition() - .name("id") - .type(GraphQLInt) - .build()) + .name("id") + .type(GraphQLInt) + .build()) .field(newFieldDefinition() - .name("childNodes") - .type(list(typeRef("Node"))) - .dataFetcher({ environment -> loader.load(environment.getSource()) }) - .build()) + .name(childNodesFieldName) + .type(list(typeRef(nodeTypeName))) + .build()) .build() + def childNodesCoordinates = FieldCoordinates.coordinates(nodeTypeName, childNodesFieldName) + def rootCoordinates = FieldCoordinates.coordinates(queryTypeName, rootFieldName) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(childNodesCoordinates, nodeDataFetcher) + .dataFetcher(rootCoordinates, new StaticDataFetcher(root)) + .build() GraphQLSchema schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(GraphQLObjectType.newObject() - .name("Query") - .field(newFieldDefinition() - .name("root") - .type(nodeType) - .dataFetcher(new StaticDataFetcher(root)) - .build()) - .build()) + .name(queryTypeName) + .field(newFieldDefinition() + .name(rootFieldName) + .type(nodeType) + .build()) + .build()) .build() - DataLoaderRegistry registry = new DataLoaderRegistry().register("childNodes", loader) + DataLoaderRegistry registry = new DataLoaderRegistry().register(childNodesFieldName, loader) ExecutionResult result = GraphQL.newGraphQL(schema) .instrumentation(new DataLoaderDispatcherInstrumentation()) @@ -146,6 +176,5 @@ class DataLoaderNodeTest extends Specification { // // but currently is this nodeLoads.size() == 3 // WOOT! - } } diff --git a/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy b/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy index bff5f7f211..eef53c9794 100644 --- a/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLCodeRegistryTest.groovy @@ -230,7 +230,7 @@ class GraphQLCodeRegistryTest extends Specification { .field(newFieldDefinition().name("codeRegistryField").type(Scalars.GraphQLString)) .field(newFieldDefinition().name("nonCodeRegistryField").type(Scalars.GraphQLString) // df comes from the field itself here - .dataFetcher(new NamedDF("nonCodeRegistryFieldValue"))) + .dataFetcher(new NamedDF("nonCodeRegistryFieldValue"))) // Retain to test Field Definition DataFetcher .field(newFieldDefinition().name("neitherSpecified").type(Scalars.GraphQLString)) .build() diff --git a/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy b/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy index 065ee682e8..3f9e396590 100644 --- a/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy @@ -15,7 +15,7 @@ class GraphQLFieldDefinitionTest extends Specification { def "dataFetcher can't be null"() { when: - newFieldDefinition().dataFetcher(null) + newFieldDefinition().dataFetcher(null) // Retain for test coverage then: def exception = thrown(AssertException) exception.getMessage().contains("dataFetcher") diff --git a/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy b/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy index d112f40fc8..3ddf718f71 100644 --- a/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy @@ -106,31 +106,29 @@ class GraphQLSchemaTest extends Specification { } static def basicSchemaBuilder() { + def queryTypeName = "QueryType" + def fooCoordinates = FieldCoordinates.coordinates(queryTypeName, "hero") + DataFetcher basicDataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return null + } + } + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, basicDataFetcher) + .build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(newObject() .name("QueryType") .field(newFieldDefinition() .name("hero") .type(GraphQLString) - .dataFetcher({ env -> null }))) + )) } - def additionalType1 = newObject() - .name("Additional1") - .field(newFieldDefinition() - .name("field") - .type(GraphQLString) - .dataFetcher({ env -> null })) - .build() - - def additionalType2 = newObject() - .name("Additional2") - .field(newFieldDefinition() - .name("field") - .type(GraphQLString) - .dataFetcher({ env -> null })) - .build() - def "clear directives works as expected"() { setup: def schemaBuilder = basicSchemaBuilder() @@ -156,7 +154,7 @@ class GraphQLSchemaTest extends Specification { schema.directives.size() == 4 } - def "clear additional types works as expected"() { + def "clear additional types works as expected"() { setup: def schemaBuilder = basicSchemaBuilder() @@ -171,12 +169,47 @@ class GraphQLSchemaTest extends Specification { schema.additionalTypes.empty when: "clear types is called with additional types" - schema = schemaBuilder.clearAdditionalTypes().additionalType(additionalType1).build() + def additional1TypeName = "Additional1" + def additional2TypeName = "Additional2" + def fieldName = "field" + def additionalType1 = newObject() + .name(additional1TypeName) + .field(newFieldDefinition() + .name(fieldName) + .type(GraphQLString)) + .build() + def additionalType2 = newObject() + .name("Additional2") + .field(newFieldDefinition() + .name(fieldName) + .type(GraphQLString)) + .build() + + def additional1Coordinates = FieldCoordinates.coordinates(additionalType1, fieldName) + DataFetcher basicDataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return null + } + } + + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(additional1Coordinates, basicDataFetcher) + .build() + + schema = schemaBuilder + .clearAdditionalTypes() + .additionalType(additionalType1) + .codeRegistry(codeRegistry) + .build() + then: schema.additionalTypes.size() == 1 when: "the schema is transformed, things are copied" - schema = schema.transform({ builder -> builder.additionalType(additionalType2) }) + def additional2Coordinates = FieldCoordinates.coordinates(additional2TypeName, fieldName) + codeRegistry = codeRegistry.transform({ builder -> builder.dataFetcher(additional2Coordinates, basicDataFetcher) }) + schema = schema.transform({ builder -> builder.additionalType(additionalType2).codeRegistry(codeRegistry) }) then: schema.additionalTypes.size() == 2 } @@ -294,7 +327,7 @@ class GraphQLSchemaTest extends Specification { it.replaceFields(fields) }) - return changeNode(context, newObjectType); + return changeNode(context, newObjectType) } return TraversalControl.CONTINUE } diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index c315553f1b..6922852633 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -28,7 +28,7 @@ class SchemaTransformerTest extends Specification { bar: String } """) - schema.getQueryType(); + schema.getQueryType() SchemaTransformer schemaTransformer = new SchemaTransformer() when: GraphQLSchema newSchema = schemaTransformer.transform(schema, new GraphQLTypeVisitorStub() { @@ -39,7 +39,7 @@ class SchemaTransformerTest extends Specification { def changedNode = fieldDefinition.transform({ builder -> builder.name("barChanged") }) return changeNode(context, changedNode) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } }) @@ -59,7 +59,7 @@ class SchemaTransformerTest extends Specification { baz: String } """) - schema.getQueryType(); + schema.getQueryType() SchemaTransformer schemaTransformer = new SchemaTransformer() when: GraphQLSchema newSchema = schemaTransformer.transform(schema, new GraphQLTypeVisitorStub() { @@ -69,7 +69,7 @@ class SchemaTransformerTest extends Specification { if (fieldDefinition.name == "baz") { return deleteNode(context) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } }) @@ -133,7 +133,7 @@ class SchemaTransformerTest extends Specification { baz: String } """) - schema.getQueryType(); + schema.getQueryType() SchemaTransformer schemaTransformer = new SchemaTransformer() when: GraphQLSchema newSchema = schemaTransformer.transform(schema, new GraphQLTypeVisitorStub() { @@ -143,7 +143,7 @@ class SchemaTransformerTest extends Specification { if (fieldDefinition.name == "baz") { return deleteNode(context) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } }) @@ -182,7 +182,7 @@ class SchemaTransformerTest extends Specification { def changedNode = fieldDefinition.transform({ builder -> builder.name("helloChanged") }) return changeNode(context, changedNode) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } @Override @@ -201,7 +201,7 @@ class SchemaTransformerTest extends Specification { @Override TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { if (node.name == "Parent") { - return changeNode(context, typeRef("ParentChanged")); + return changeNode(context, typeRef("ParentChanged")) } return super.visitGraphQLTypeReference(node, context) } @@ -303,21 +303,30 @@ type SubChildChanged { def queryObject = newObject() .name("Query") .field({ builder -> - builder.name("foo").type(Scalars.GraphQLString).dataFetcher(new DataFetcher() { - @Override - Object get(DataFetchingEnvironment environment) throws Exception { - return "bar"; - } - }) - }).build(); + builder.name("foo") + .type(Scalars.GraphQLString) + }).build() + + def fooCoordinates = FieldCoordinates.coordinates("Query", "foo") + DataFetcher dataFetcher = new DataFetcher() { + @Override + Object get(DataFetchingEnvironment environment) throws Exception { + return "bar" + } + } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) + .build() def schemaObject = newSchema() + .codeRegistry(codeRegistry) .query(queryObject) .build() when: def result = GraphQL.newGraphQL(schemaObject) - .build().execute(''' + .build() + .execute(''' { foo } ''').getData() @@ -336,14 +345,20 @@ type SubChildChanged { return TraversalControl.CONTINUE } }) + + def fooTransformedCoordinates = FieldCoordinates.coordinates("Query", "fooChanged") + codeRegistry = codeRegistry.transform({it.dataFetcher(fooTransformedCoordinates, dataFetcher)}) + newSchema = newSchema.transform({ + builder -> builder.codeRegistry(codeRegistry) + }) result = GraphQL.newGraphQL(newSchema) - .build().execute(''' + .build() + .execute(''' { fooChanged } ''').getData() then: (result as Map)['fooChanged'] == 'bar' - } def "transformed schema can be executed"() { @@ -430,7 +445,7 @@ type SubChildChanged { if (fieldDefinition.name == "billingStatus") { return deleteNode(context) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } }) @@ -448,11 +463,11 @@ type SubChildChanged { @Override TraversalControl visitGraphQLDirective(GraphQLDirective node, TraverserContext context) { - if ("internalnote".equals(node.getName())) { + if ("internalnote" == node.getName()) { // this deletes the declaration and the two usages of it - deleteNode(context); + deleteNode(context) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } } @@ -628,7 +643,7 @@ type Query { @Override TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { - if (node.getName().startsWith("__")) return TraversalControl.ABORT; + if (node.getName().startsWith("__")) return TraversalControl.ABORT node = node.transform({ b -> b.name(node.getName().toUpperCase()) }) return changeNode(context, node) } @@ -716,7 +731,7 @@ type Query { @Override TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { - if (node.getName().equals('ToDel')) { + if (node.getName() == 'ToDel') { return deleteNode(context) } return TraversalControl.CONTINUE @@ -774,7 +789,7 @@ type Query { if (node.name == "__Field") { return changeNode(context, node.transform({ it.name("__FieldChanged") })) } - return TraversalControl.CONTINUE; + return TraversalControl.CONTINUE } } @@ -855,7 +870,7 @@ type Query { @Override TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { - if (node.getName().equals("Foo")) { + if (node.getName() == "Foo") { GraphQLScalarType newNode = node.transform({sc -> sc.name("Bar")}) return changeNode(context, newNode) } @@ -885,7 +900,7 @@ type Query { @Override TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { - if (node.getName().equals("Foo")) { + if (node.getName() == "Foo") { GraphQLScalarType newNode = node.transform({sc -> sc.name("Bar")}) return changeNode(context, newNode) } diff --git a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy index 1c98b1f85d..d80f7e3b6d 100644 --- a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy +++ b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy @@ -6,6 +6,8 @@ import graphql.StarWarsSchema import graphql.execution.AsyncExecutionStrategy import graphql.introspection.IntrospectionQuery import graphql.language.Field +import graphql.schema.DataFetcher +import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLObjectType @@ -310,17 +312,28 @@ enum Episode { .field(newInputObjectField().name("closedField").type(GraphQLString)) .build() - def inputQueryType = GraphQLObjectType.newObject().name("InputQuery") - .field(newFieldDefinition().name("hello").type(GraphQLString) - .argument(newArgument().name("arg").type(inputType)) - .dataFetcher({ env -> return "world" }) - ) - .build() + DataFetcher inputDataFetcher = { env -> return "world" } + + def inputQueryType = GraphQLObjectType.newObject() + .name("InputQuery") + .field(newFieldDefinition() + .name("hello") + .type(GraphQLString) + .argument(newArgument() + .name("arg") + .type(inputType)) + ).build() def "ensure input field are blocked"() { when: + def inputTypeCoordinates = FieldCoordinates.coordinates("InputQuery", "hello") + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(inputTypeCoordinates, inputDataFetcher) + .build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(inputQueryType) .build() @@ -340,9 +353,7 @@ enum Episode { er.getData() == ["hello": "world"] when: - GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .fieldVisibility(ban(['InputType.closedField'])) - .build() + codeRegistry = codeRegistry.transform({builder -> builder.fieldVisibility(ban(['InputType.closedField']))}) schema = GraphQLSchema.newSchema() .query(inputQueryType) .codeRegistry(codeRegistry) From dc41d6ae9a26470bf1a6a104d94f6dfa365577b0 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Sep 2022 21:05:11 +1000 Subject: [PATCH 120/294] Fixing more deprecated field definition data fetcher builders --- src/test/groovy/graphql/MutationSchema.java | 56 ++++++---- .../groovy/graphql/NestedInputSchema.java | 57 +++++----- src/test/groovy/graphql/RelaySchema.java | 16 ++- .../groovy/graphql/ScalarsQuerySchema.java | 37 +++--- src/test/groovy/graphql/StarWarsData.groovy | 1 - src/test/groovy/graphql/StarWarsSchema.java | 46 +++++--- .../dataloader/DeepDataFetchers.java | 105 ++++++++++-------- 7 files changed, 187 insertions(+), 131 deletions(-) diff --git a/src/test/groovy/graphql/MutationSchema.java b/src/test/groovy/graphql/MutationSchema.java index 2683652331..5956d7c56d 100644 --- a/src/test/groovy/graphql/MutationSchema.java +++ b/src/test/groovy/graphql/MutationSchema.java @@ -1,6 +1,9 @@ package graphql; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; @@ -31,8 +34,8 @@ public void setTheNumber(int theNumber) { } public static class SubscriptionRoot { - List result = new ArrayList(); - List subscribers = new ArrayList(); + List result = new ArrayList<>(); + List subscribers = new ArrayList<>(); NumberHolder numberHolder; public SubscriptionRoot(int initalNumber) { @@ -89,23 +92,13 @@ public List getResult() { .type(numberHolderType) .argument(newArgument() .name("newNumber") - .type(GraphQLInt)) - .dataFetcher(environment -> { - Integer newNumber = environment.getArgument("newNumber"); - SubscriptionRoot root = environment.getSource(); - return root.changeNumber(newNumber); - })) + .type(GraphQLInt))) .field(newFieldDefinition() .name("failToChangeTheNumber") .type(numberHolderType) .argument(newArgument() .name("newNumber") - .type(GraphQLInt)) - .dataFetcher(environment -> { - Integer newNumber = environment.getArgument("newNumber"); - SubscriptionRoot root = environment.getSource(); - return root.failToChangeTheNumber(newNumber); - })) + .type(GraphQLInt))) .build(); public static GraphQLObjectType subscriptionType = GraphQLObjectType.newObject() @@ -115,16 +108,37 @@ public List getResult() { .type(numberHolderType) .argument(newArgument() .name("clientId") - .type(GraphQLInt)) - .dataFetcher(environment -> { - Integer clientId = environment.getArgument("clientId"); - SubscriptionRoot subscriptionRoot = environment.getSource(); - subscriptionRoot.subscribeToNumberChanges(clientId); - return subscriptionRoot.getNumberHolder(); - })) + .type(GraphQLInt))) + .build(); + + static FieldCoordinates changeMutationCoordinates = FieldCoordinates.coordinates("mutationType", "changeTheNumber"); + static DataFetcher changeMutationDataFetcher = environment -> { + Integer newNumber = environment.getArgument("newNumber"); + SubscriptionRoot root = environment.getSource(); + return root.changeNumber(newNumber); + }; + static FieldCoordinates failToChangeMutationCoordinates = FieldCoordinates.coordinates("mutationType", "failToChangeTheNumber"); + static DataFetcher failToChangeMutationDataFetcher = environment -> { + Integer newNumber = environment.getArgument("newNumber"); + SubscriptionRoot root = environment.getSource(); + return root.failToChangeTheNumber(newNumber); + }; + static FieldCoordinates changeNumberSubscribeCoordinates = FieldCoordinates.coordinates("subscriptionType", "changeNumberSubscribe"); + static DataFetcher changeNumberSubscribeDataFetcher = environment -> { + Integer clientId = environment.getArgument("clientId"); + SubscriptionRoot subscriptionRoot = environment.getSource(); + subscriptionRoot.subscribeToNumberChanges(clientId); + return subscriptionRoot.getNumberHolder(); + }; + + static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(changeMutationCoordinates, changeMutationDataFetcher) + .dataFetcher(failToChangeMutationCoordinates, failToChangeMutationDataFetcher) + .dataFetcher(changeNumberSubscribeCoordinates, changeNumberSubscribeDataFetcher) .build(); public static GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) .query(queryType) .mutation(mutationType) .subscription(subscriptionType) diff --git a/src/test/groovy/graphql/NestedInputSchema.java b/src/test/groovy/graphql/NestedInputSchema.java index add9b44c4d..12e690a433 100644 --- a/src/test/groovy/graphql/NestedInputSchema.java +++ b/src/test/groovy/graphql/NestedInputSchema.java @@ -1,6 +1,9 @@ package graphql; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; @@ -14,47 +17,49 @@ public class NestedInputSchema { - public static GraphQLSchema createSchema() { + GraphQLObjectType root = rootType(); + FieldCoordinates valueCoordinates = FieldCoordinates.coordinates("Root", "value"); + DataFetcher valueDataFetcher = environment -> { + Integer initialValue = environment.getArgument("initialValue"); + Map filter = environment.getArgument("filter"); + if (filter != null) { + if (filter.containsKey("even")) { + Boolean even = (Boolean) filter.get("even"); + if (even && (initialValue % 2 != 0)) { + return 0; + } else if (!even && (initialValue % 2 == 0)) { + return 0; + } + } + if (filter.containsKey("range")) { + Map range = (Map) filter.get("range"); + if (initialValue < range.get("lowerBound") || + initialValue > range.get("upperBound")) { + return 0; + } + } + } + return initialValue; + }; - GraphQLObjectType root = rootType(); + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(valueCoordinates, valueDataFetcher) + .build(); return GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(root) .build(); } - @SuppressWarnings("unchecked") public static GraphQLObjectType rootType() { return GraphQLObjectType.newObject() - .name("Root") .field(GraphQLFieldDefinition.newFieldDefinition() .name("value") .type(GraphQLInt) - .dataFetcher(environment -> { - Integer initialValue = environment.getArgument("initialValue"); - Map filter = environment.getArgument("filter"); - if (filter != null) { - if (filter.containsKey("even")) { - Boolean even = (Boolean) filter.get("even"); - if (even && (initialValue % 2 != 0)) { - return 0; - } else if (!even && (initialValue % 2 == 0)) { - return 0; - } - } - if (filter.containsKey("range")) { - Map range = (Map) filter.get("range"); - if (initialValue < range.get("lowerBound") || - initialValue > range.get("upperBound")) { - return 0; - } - } - } - return initialValue; - }) .argument(GraphQLArgument.newArgument() .name("intialValue") .type(GraphQLInt) diff --git a/src/test/groovy/graphql/RelaySchema.java b/src/test/groovy/graphql/RelaySchema.java index 751afa8ebe..52b5526277 100644 --- a/src/test/groovy/graphql/RelaySchema.java +++ b/src/test/groovy/graphql/RelaySchema.java @@ -1,6 +1,9 @@ package graphql; import graphql.relay.Relay; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; @@ -57,15 +60,18 @@ public class RelaySchema { .argument(newArgument() .name("id") .description("id of the thing") - .type(GraphQLNonNull.nonNull(GraphQLString))) - .dataFetcher(environment -> { - //TODO: implement - return null; - })) + .type(GraphQLNonNull.nonNull(GraphQLString)))) .build(); + public static FieldCoordinates thingCoordinates = FieldCoordinates.coordinates("RelayQuery", "thing"); + public static DataFetcher thingDataFetcher = environment -> null; + + public static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(thingCoordinates, thingDataFetcher) + .build(); public static GraphQLSchema Schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(RelayQueryType) .additionalType(Relay.pageInfoType) .build(); diff --git a/src/test/groovy/graphql/ScalarsQuerySchema.java b/src/test/groovy/graphql/ScalarsQuerySchema.java index dcf339afa8..6d2cd8ce9f 100644 --- a/src/test/groovy/graphql/ScalarsQuerySchema.java +++ b/src/test/groovy/graphql/ScalarsQuerySchema.java @@ -2,6 +2,8 @@ import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; @@ -12,53 +14,56 @@ public class ScalarsQuerySchema { - public static final DataFetcher inputDF = environment -> environment.getArgument("input"); + public static final DataFetcher inputDF = environment -> environment.getArgument("input"); public static final GraphQLObjectType queryType = newObject() .name("QueryType") - /** Static Scalars */ + // Static scalars .field(newFieldDefinition() .name("floatNaN") .type(Scalars.GraphQLFloat) .staticValue(Double.NaN)) - - - /** Scalars with input of same type, value echoed back */ + // Scalars with input of same type, value echoed back .field(newFieldDefinition() .name("floatNaNInput") .type(Scalars.GraphQLFloat) .argument(newArgument() .name("input") - .type(GraphQLNonNull.nonNull(Scalars.GraphQLFloat))) - .dataFetcher(inputDF)) + .type(GraphQLNonNull.nonNull(Scalars.GraphQLFloat)))) .field(newFieldDefinition() .name("stringInput") .type(Scalars.GraphQLString) .argument(newArgument() .name("input") - .type(GraphQLNonNull.nonNull(Scalars.GraphQLString))) - .dataFetcher(inputDF)) - - - /** Scalars with input of String, cast to scalar */ + .type(GraphQLNonNull.nonNull(Scalars.GraphQLString)))) + // Scalars with input of String, cast to scalar .field(newFieldDefinition() .name("floatString") .type(Scalars.GraphQLFloat) .argument(newArgument() .name("input") - .type(Scalars.GraphQLString)) - .dataFetcher(inputDF)) + .type(Scalars.GraphQLString))) .field(newFieldDefinition() .name("intString") .type(Scalars.GraphQLInt) .argument(newArgument() .name("input") - .type(Scalars.GraphQLString)) - .dataFetcher(inputDF)) + .type(Scalars.GraphQLString))) .build(); + static FieldCoordinates floatNaNCoordinates = FieldCoordinates.coordinates("QueryType", "floatNaNInput"); + static FieldCoordinates stringInputCoordinates = FieldCoordinates.coordinates("QueryType", "stringInput"); + static FieldCoordinates floatStringCoordinates = FieldCoordinates.coordinates("QueryType", "floatString"); + static FieldCoordinates intStringCoordinates = FieldCoordinates.coordinates("QueryType", "intString"); + static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(floatNaNCoordinates, inputDF) + .dataFetcher(stringInputCoordinates, inputDF) + .dataFetcher(floatStringCoordinates, inputDF) + .dataFetcher(intStringCoordinates, inputDF) + .build(); public static final GraphQLSchema scalarsQuerySchema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(queryType) .build(); } diff --git a/src/test/groovy/graphql/StarWarsData.groovy b/src/test/groovy/graphql/StarWarsData.groovy index 2bfd4accc9..d4a406d565 100644 --- a/src/test/groovy/graphql/StarWarsData.groovy +++ b/src/test/groovy/graphql/StarWarsData.groovy @@ -94,7 +94,6 @@ class StarWarsData { } } - static DataFetcher droidDataFetcher = new DataFetcher() { @Override Object get(DataFetchingEnvironment environment) { diff --git a/src/test/groovy/graphql/StarWarsSchema.java b/src/test/groovy/graphql/StarWarsSchema.java index fe30898bac..8563df2e96 100644 --- a/src/test/groovy/graphql/StarWarsSchema.java +++ b/src/test/groovy/graphql/StarWarsSchema.java @@ -1,12 +1,14 @@ package graphql; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; -import graphql.schema.GraphqlTypeComparatorRegistry; import graphql.schema.StaticDataFetcher; import static graphql.Scalars.GraphQLString; @@ -24,7 +26,6 @@ public class StarWarsSchema { - public static GraphQLEnumType episodeEnum = newEnum() .name("Episode") .description("One of the films in the Star Wars Trilogy") @@ -73,8 +74,7 @@ public class StarWarsSchema { .field(newFieldDefinition() .name("friends") .description("The friends of the human, or an empty list if they have none.") - .type(list(characterInterface)) - .dataFetcher(StarWarsData.getFriendsDataFetcher())) + .type(list(characterInterface))) .field(newFieldDefinition() .name("appearsIn") .description("Which movies they appear in.") @@ -101,8 +101,7 @@ public class StarWarsSchema { .field(newFieldDefinition() .name("friends") .description("The friends of the droid, or an empty list if they have none.") - .type(list(characterInterface)) - .dataFetcher(StarWarsData.getFriendsDataFetcher())) + .type(list(characterInterface))) .field(newFieldDefinition() .name("appearsIn") .description("Which movies they appear in.") @@ -132,24 +131,21 @@ public class StarWarsSchema { .argument(newArgument() .name("episode") .description("If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.") - .type(episodeEnum)) - .dataFetcher(new StaticDataFetcher(StarWarsData.getArtoo()))) + .type(episodeEnum))) .field(newFieldDefinition() .name("human") .type(humanType) .argument(newArgument() .name("id") .description("id of the human") - .type(nonNull(GraphQLString))) - .dataFetcher(StarWarsData.getHumanDataFetcher())) + .type(nonNull(GraphQLString)))) .field(newFieldDefinition() .name("droid") .type(droidType) .argument(newArgument() .name("id") .description("id of the droid") - .type(nonNull(GraphQLString))) - .dataFetcher(StarWarsData.getDroidDataFetcher())) + .type(nonNull(GraphQLString)))) .comparatorRegistry(BY_NAME_REGISTRY) .build(); @@ -160,12 +156,34 @@ public class StarWarsSchema { .type(characterInterface) .argument(newArgument() .name("input") - .type(inputHumanType)) - .dataFetcher(new StaticDataFetcher(StarWarsData.getArtoo()))) + .type(inputHumanType))) .comparatorRegistry(BY_NAME_REGISTRY) .build(); + public static FieldCoordinates humanFriendsCoordinates = FieldCoordinates.coordinates("Human", "friends"); + public static DataFetcher humanFriendsDataFetcher = StarWarsData.getFriendsDataFetcher(); + public static FieldCoordinates droidFriendsCoordinates = FieldCoordinates.coordinates("Droid", "friends"); + public static DataFetcher droidFriendsDataFetcher = StarWarsData.getFriendsDataFetcher(); + public static FieldCoordinates heroCoordinates = FieldCoordinates.coordinates("QueryType", "hero"); + public static DataFetcher heroDataFetcher = new StaticDataFetcher(StarWarsData.getArtoo()); + public static FieldCoordinates humanCoordinates = FieldCoordinates.coordinates("QueryType", "human"); + public static DataFetcher humanDataFetcher = StarWarsData.getHumanDataFetcher(); + public static FieldCoordinates droidCoordinates = FieldCoordinates.coordinates("QueryType", "droid"); + public static DataFetcher droidDataFetcher = StarWarsData.getDroidDataFetcher(); + public static FieldCoordinates createHumanCoordinates = FieldCoordinates.coordinates("MutationType", "createHuman"); + public static DataFetcher createHumanDataFetcher = new StaticDataFetcher(StarWarsData.getArtoo()); + + public static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(humanFriendsCoordinates, humanFriendsDataFetcher) + .dataFetcher(droidFriendsCoordinates, droidFriendsDataFetcher) + .dataFetcher(heroCoordinates, heroDataFetcher) + .dataFetcher(humanCoordinates, humanDataFetcher) + .dataFetcher(droidCoordinates, droidDataFetcher) + .dataFetcher(createHumanCoordinates, createHumanDataFetcher) + .build(); + public static GraphQLSchema starWarsSchema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(queryType) .mutation(mutationType) .build(); diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DeepDataFetchers.java b/src/test/groovy/graphql/execution/instrumentation/dataloader/DeepDataFetchers.java index d090425b51..874700f32a 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DeepDataFetchers.java +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DeepDataFetchers.java @@ -5,68 +5,77 @@ import java.util.function.Supplier; import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLTypeReference; public class DeepDataFetchers { - private static CompletableFuture supplyAsyncWithSleep(Supplier supplier) { - Supplier sleepSome = sleepSome(supplier); - return CompletableFuture.supplyAsync(sleepSome); - } + private static CompletableFuture supplyAsyncWithSleep(Supplier supplier) { + Supplier sleepSome = sleepSome(supplier); + return CompletableFuture.supplyAsync(sleepSome); + } - private static Supplier sleepSome(Supplier supplier) { - return () -> { - try { - Thread.sleep(10L); - return supplier.get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }; - } + private static Supplier sleepSome(Supplier supplier) { + return () -> { + try { + Thread.sleep(10L); + return supplier.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } - public GraphQLSchema schema() { - DataFetcher>> slowFetcher = environment -> - supplyAsyncWithSleep(HashMap::new); + public GraphQLSchema schema() { + GraphQLFieldDefinition selfField = GraphQLFieldDefinition.newFieldDefinition() + .name("self") + .type(GraphQLTypeReference.typeRef("Query")) + .build(); - GraphQLFieldDefinition selfField = GraphQLFieldDefinition.newFieldDefinition() - .name("self") - .type(GraphQLTypeReference.typeRef("Query")) - .dataFetcher(slowFetcher) - .build(); + GraphQLObjectType query = GraphQLObjectType.newObject() + .name("Query") + .field(selfField) + .build(); - GraphQLObjectType query = GraphQLObjectType.newObject() - .name("Query") - .field(selfField) - .build(); + FieldCoordinates selfCoordinates = FieldCoordinates.coordinates("Query", "self"); + DataFetcher>> slowFetcher = environment -> + supplyAsyncWithSleep(HashMap::new); - return GraphQLSchema.newSchema().query(query).build(); - } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(selfCoordinates, slowFetcher) + .build(); - public String buildQuery(Integer depth) { - StringBuilder sb = new StringBuilder(); - sb.append("query {"); - for (Integer i = 0; i < depth; i++) { - sb.append("self {"); - } - sb.append("__typename"); - for (Integer i = 0; i < depth; i++) { - sb.append("}"); + return GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build(); } - sb.append("}"); - return sb.toString(); - } + public String buildQuery(Integer depth) { + StringBuilder sb = new StringBuilder(); + sb.append("query {"); + for (Integer i = 0; i < depth; i++) { + sb.append("self {"); + } + sb.append("__typename"); + for (Integer i = 0; i < depth; i++) { + sb.append("}"); + } + sb.append("}"); + + return sb.toString(); + } - public HashMap buildResponse(Integer depth) { - HashMap level = new HashMap<>(); - if (depth == 0) { - level.put("__typename", "Query"); - } else { - level.put("self", buildResponse(depth - 1)); + public HashMap buildResponse(Integer depth) { + HashMap level = new HashMap<>(); + if (depth == 0) { + level.put("__typename", "Query"); + } else { + level.put("self", buildResponse(depth - 1)); + } + return level; } - return level; - } } From 214aa2ce308adeac6318865e2a3798e908b12b72 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 6 Sep 2022 07:47:46 +1000 Subject: [PATCH 121/294] Fix GraphQLTest deprecated field definition datafetcher usage --- src/test/groovy/graphql/GraphQLTest.groovy | 291 +++++++++++++-------- 1 file changed, 186 insertions(+), 105 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index af9f0d0595..c7c2b1ba82 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -24,6 +24,8 @@ import graphql.execution.preparsed.NoOpPreparsedDocumentProvider import graphql.language.SourceLocation import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment +import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLDirective import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition @@ -58,7 +60,7 @@ import static graphql.schema.GraphQLTypeReference.typeRef class GraphQLTest extends Specification { - GraphQLSchema simpleSchema() { + static GraphQLSchema simpleSchema() { GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() .name("hello") .type(GraphQLString) @@ -239,14 +241,22 @@ class GraphQLTest extends Specification { set.add("One") set.add("Two") + def queryType = "QueryType" + def fieldName = "set" + def fieldCoordinates = FieldCoordinates.coordinates(queryType, fieldName) + DataFetcher dataFetcher = { set } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fieldCoordinates, dataFetcher) + .build() + def schema = newSchema() + .codeRegistry(codeRegistry) .query(newObject() - .name("QueryType") + .name(queryType) .field(newFieldDefinition() - .name("set") - .type(list(GraphQLString)) - .dataFetcher({ set }))) - .build() + .name(fieldName) + .type(list(GraphQLString))) + ).build() when: def data = GraphQL.newGraphQL(schema).build().execute("query { set }").data @@ -258,14 +268,30 @@ class GraphQLTest extends Specification { def "document with two operations executes specified operation"() { given: - GraphQLSchema schema = newSchema().query( - newObject() - .name("RootQueryType") - .field(newFieldDefinition().name("field1").type(GraphQLString).dataFetcher(new StaticDataFetcher("value1"))) - .field(newFieldDefinition().name("field2").type(GraphQLString).dataFetcher(new StaticDataFetcher("value2"))) - ) + def queryType = "RootQueryType" + def field1Name = "field1" + def field2Name = "field2" + def field1Coordinates = FieldCoordinates.coordinates(queryType, field1Name) + def field2Coordinates = FieldCoordinates.coordinates(queryType, field2Name) + DataFetcher field1DataFetcher = new StaticDataFetcher("value1") + DataFetcher field2DataFetcher = new StaticDataFetcher("value2") + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(field1Coordinates, field1DataFetcher) + .dataFetcher(field2Coordinates, field2DataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(field1Name) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(field2Name) + .type(GraphQLString)) + ).build() + def query = """ query Query1 { field1 } query Query2 { field2 } @@ -352,18 +378,25 @@ class GraphQLTest extends Specification { def "query with int literal too large"() { given: - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(GraphQLInt).build()) - .dataFetcher({ return it.getArgument("bar") }) - )) + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + DataFetcher dataFetcher = { env -> env.getArgument("bar") } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name("QueryType") + .field(newFieldDefinition() + .name("foo") + .type(GraphQLInt) + .argument(newArgument().name("bar").type(GraphQLInt).build())) + ).build() def query = "{foo(bar: 12345678910)}" + when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -375,19 +408,24 @@ class GraphQLTest extends Specification { @SuppressWarnings("GroovyAssignabilityCheck") def "query with missing argument results in arguments map missing the key"() { given: + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) def dataFetcher = Mock(DataFetcher) - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(GraphQLInt).build()) - .dataFetcher(dataFetcher) - )) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(GraphQLInt).build())) + ).build() def query = "{foo}" + when: GraphQL.newGraphQL(schema).build().execute(query) @@ -401,20 +439,25 @@ class GraphQLTest extends Specification { @SuppressWarnings("GroovyAssignabilityCheck") def "query with null argument results in arguments map with value null "() { given: + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) def dataFetcher = Mock(DataFetcher) - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(GraphQLInt).build()) - .dataFetcher(dataFetcher) - )) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(GraphQLInt).build())) + ).build() def query = "{foo(bar: null)}" DataFetchingEnvironment dataFetchingEnvironment + when: GraphQL.newGraphQL(schema).build().execute(query) @@ -430,21 +473,27 @@ class GraphQLTest extends Specification { @SuppressWarnings("GroovyAssignabilityCheck") def "query with missing key in an input object result in a map with missing key"() { given: - def dataFetcher = Mock(DataFetcher) def inputObject = newInputObject().name("bar") .field(newInputObjectField().name("someKey").type(GraphQLString).build()) .field(newInputObjectField().name("otherKey").type(GraphQLString).build()).build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(inputObject).build()) - .dataFetcher(dataFetcher) - )) + + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + def dataFetcher = Mock(DataFetcher) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(inputObject).build())) + ).build() def query = "{foo(bar: {someKey: \"value\"})}" when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -463,22 +512,28 @@ class GraphQLTest extends Specification { @SuppressWarnings("GroovyAssignabilityCheck") def "query with null value in an input object result in a map with null as value"() { given: - def dataFetcher = Mock(DataFetcher) def inputObject = newInputObject().name("bar") .field(newInputObjectField().name("someKey").type(GraphQLString).build()) .field(newInputObjectField().name("otherKey").type(GraphQLString).build()).build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(inputObject).build()) - .dataFetcher(dataFetcher) - )) + + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + def dataFetcher = Mock(DataFetcher) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(inputObject).build())) + ).build() def query = "{foo(bar: {someKey: \"value\", otherKey: null})}" + when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -496,22 +551,28 @@ class GraphQLTest extends Specification { def "query with missing List input field results in a map with a missing key"() { given: - def dataFetcher = Mock(DataFetcher) def inputObject = newInputObject().name("bar") .field(newInputObjectField().name("list").type(list(GraphQLString)).build()) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(inputObject).build()) - .dataFetcher(dataFetcher) - )) + + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + def dataFetcher = Mock(DataFetcher) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(inputObject).build())) + ).build() def query = "{foo(bar: {})}" + when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -527,22 +588,28 @@ class GraphQLTest extends Specification { def "query with null List input field results in a map with null as key"() { given: - def dataFetcher = Mock(DataFetcher) def inputObject = newInputObject().name("bar") .field(newInputObjectField().name("list").type(list(GraphQLString)).build()) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(inputObject).build()) - .dataFetcher(dataFetcher) - )) + + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + def dataFetcher = Mock(DataFetcher) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(inputObject).build())) + ).build() def query = "{foo(bar: {list: null})}" + when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -559,22 +626,28 @@ class GraphQLTest extends Specification { def "query with List containing null input field results in a map with a list containing null"() { given: - def dataFetcher = Mock(DataFetcher) def inputObject = newInputObject().name("bar") .field(newInputObjectField().name("list").type(list(GraphQLString)).build()) .build() - GraphQLSchema schema = newSchema().query( - newObject() - .name("QueryType") - .field( - newFieldDefinition() - .name("foo") - .type(GraphQLInt) - .argument(newArgument().name("bar").type(inputObject).build()) - .dataFetcher(dataFetcher) - )) + + def queryType = "QueryType" + def fooName = "foo" + def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) + def dataFetcher = Mock(DataFetcher) + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fooCoordinates, dataFetcher) .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) + .field(newFieldDefinition() + .name(fooName) + .type(GraphQLInt) + .argument(newArgument().name("bar").type(inputObject).build())) + ).build() def query = "{foo(bar: {list: [null]})}" + when: def result = GraphQL.newGraphQL(schema).build().execute(query) @@ -939,18 +1012,26 @@ class GraphQLTest extends Specification { def "query with triple quoted multi line strings"() { given: + def queryType = "Query" + def fieldName = "hello" + def fieldCoordinates = FieldCoordinates.coordinates(queryType, fieldName) + DataFetcher dataFetcher = { env -> env.getArgument("arg") } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fieldCoordinates, dataFetcher) + .build() + GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() - .name("hello") + .name(fieldName) .type(GraphQLString) .argument(newArgument().name("arg").type(GraphQLString)) - .dataFetcher({ env -> env.getArgument("arg") } - ) - GraphQLSchema schema = newSchema().query( - newObject() - .name("Query") + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() + .name(queryType) .field(fieldDefinition) - .build() - ).build() + .build()) + .build() when: def result = GraphQL.newGraphQL(schema).build().execute('''{ hello(arg:""" From 03776d73d3448f5464daab0a848fb78cce641c1a Mon Sep 17 00:00:00 2001 From: Diego Manzanarez Date: Mon, 5 Sep 2022 18:15:34 -0500 Subject: [PATCH 122/294] Moved package json to workflows folder and removed unused dependencies --- .github/workflows/create_job.js | 6 ++++-- .github/workflows/invoke_test_runner.yml | 10 +++++++++- .github/workflows/package.json | 14 ++++++++++++++ package.json | 20 -------------------- 4 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/package.json delete mode 100644 package.json diff --git a/.github/workflows/create_job.js b/.github/workflows/create_job.js index 7112ad32ef..e14f39f828 100644 --- a/.github/workflows/create_job.js +++ b/.github/workflows/create_job.js @@ -6,14 +6,16 @@ const client = new CloudTasksClient(); const constructPayload = () => { const jobId = String(uuidv4()); const commitHash = process.env.COMMIT_HASH; + const branch = process.env.BRANCH; const classes = process.env.CLASSES; const pullRequestNumber = process.env.PULL_REQUEST_NUMBER; const payloadStructure = { "jobId": jobId, - "commitHash": commitHash, + "commitHash": commitHash.trim(), + "branch": branch.trim(), "classes": convertClassesStringToArray(classes), - "pullRequest": pullRequestNumber, + "pullRequest": pullRequestNumber.trim(), } return payloadStructure; } diff --git a/.github/workflows/invoke_test_runner.yml b/.github/workflows/invoke_test_runner.yml index d9e81e2cf2..0792700115 100644 --- a/.github/workflows/invoke_test_runner.yml +++ b/.github/workflows/invoke_test_runner.yml @@ -5,6 +5,9 @@ on: - master workflow_dispatch: inputs: + BRANCH_INPUT: + description: 'Branch' + required: true COMMIT_HASH_INPUT: description: 'Commit hash' required: true @@ -22,6 +25,7 @@ env: WORKFLOW_URL: ${{ secrets.WORKFLOW_URL }} #Payload variables COMMIT_HASH: ${{ (github.sha) }} + BRANCH: ${{ (github.ref_name) }} CLASSES: ${{ (github.event.inputs.CLASSES_TO_EXECUTE_INPUT) }} PULL_REQUEST_NUMBER: ${{ (github.event.inputs.PULL_REQUEST_NUMBER_INPUT) }} @@ -33,12 +37,16 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '14' - - run: npm install + - run: npm install --prefix .github/workflows - name: Update COMMIT_HASH if: ${{ github.event_name == 'workflow_dispatch' }} run: echo "COMMIT_HASH=${{ (github.event.inputs.COMMIT_HASH_INPUT) }} " >> $GITHUB_ENV + - name: Update BRANCH + if: ${{ github.event_name == 'workflow_dispatch' }} + run: echo "BRANCH=${{ (github.event.inputs.BRANCH_INPUT) }} " >> $GITHUB_ENV + - id: 'auth' name: 'Authenticate to Google Cloud' uses: google-github-actions/auth@v0.4.0 diff --git a/.github/workflows/package.json b/.github/workflows/package.json new file mode 100644 index 0000000000..71ef804476 --- /dev/null +++ b/.github/workflows/package.json @@ -0,0 +1,14 @@ +{ + "name": "workflow-testrunner-tasksenqueuer", + "private": true, + "engines": { + "node": ">=12.0.0" + }, + "files": [ + "*.js" + ], + "dependencies": { + "@google-cloud/tasks": "^3.0.0", + "uuid": "^8.0.0" + } +} diff --git a/package.json b/package.json deleted file mode 100644 index 90987a264a..0000000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "appengine-cloudtasks", - "description": "Google App Engine Cloud Tasks example.", - "license": "Apache-2.0", - "author": "Google Inc.", - "private": true, - "engines": { - "node": ">=12.0.0" - }, - "files": [ - "*.js" - ], - "dependencies": { - "@google-cloud/tasks": "^3.0.0", - "body-parser": "^1.18.3", - "express": "^4.16.3", - "yenv": "^3.0.1", - "uuid": "^8.0.0" - } -} From be2de8807b83efd9526a2a811ad2bb360cf94882 Mon Sep 17 00:00:00 2001 From: Antoine Boyer Date: Tue, 6 Sep 2022 16:05:55 -0700 Subject: [PATCH 123/294] Fix bug where parsing a sdl with @skip definition would result in 2 directive definitions on the GraphQLSchema (#2940) --- .../schema/idl/SchemaGeneratorHelper.java | 7 +++++ .../schema/idl/SchemaGeneratorTest.groovy | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java b/src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java index 2fcb1bf072..6ebc071501 100644 --- a/src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java +++ b/src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java @@ -80,8 +80,10 @@ import static graphql.Assert.assertNotNull; import static graphql.Directives.DEPRECATED_DIRECTIVE_DEFINITION; +import static graphql.Directives.IncludeDirective; import static graphql.Directives.NO_LONGER_SUPPORTED; import static graphql.Directives.SPECIFIED_BY_DIRECTIVE_DEFINITION; +import static graphql.Directives.SkipDirective; import static graphql.Directives.SpecifiedByDirective; import static graphql.collect.ImmutableKit.emptyList; import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; @@ -1055,6 +1057,11 @@ Set buildAdditionalDirectiveDefinitions(BuildContext buildCtx) TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry(); for (DirectiveDefinition directiveDefinition : typeRegistry.getDirectiveDefinitions().values()) { + if (IncludeDirective.getName().equals(directiveDefinition.getName()) + || SkipDirective.getName().equals(directiveDefinition.getName())) { + // skip and include directives are added by default to the GraphQLSchema via the GraphQLSchema builder. + continue; + } GraphQLDirective directive = buildDirectiveDefinitionFromAst(buildCtx, directiveDefinition, inputTypeFactory(buildCtx)); buildCtx.addDirectiveDefinition(directive); additionalDirectives.add(directive); diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index a6e3afc89c..010a9aee77 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -2500,4 +2500,35 @@ class SchemaGeneratorTest extends Specification { then: noExceptionThrown() } + + def "skip and include should be added to the schema only if not already defined"() { + def sdl = ''' + "Directs the executor to skip this field or fragment when the `if`'argument is true." + directive @skip( + "Skipped when true." + if: Boolean! + ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + + "Directs the executor to include this field or fragment only when the `if` argument is true" + directive @include( + "Included when true." + if: Boolean! + ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + hello: String + } + ''' + when: + def schema = TestUtil.schema(sdl) + then: + schema.getDirectives().findAll { it.name == "skip" }.size() == 1 + schema.getDirectives().findAll { it.name == "include" }.size() == 1 + + and: + def newSchema = GraphQLSchema.newSchema(schema).build() + then: + newSchema.getDirectives().findAll { it.name == "skip" }.size() == 1 + newSchema.getDirectives().findAll { it.name == "include" }.size() == 1 + } } From 6af2ecd6f0928a0cc2dfa5299ad4e08583d4681e Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 12:01:14 +1000 Subject: [PATCH 124/294] Update tests to remove deprecated typeResolver builder for unions and interfaces --- .../graphql/schema/GraphQLInterfaceType.java | 8 +- .../java/graphql/schema/GraphQLUnionType.java | 7 ++ src/test/groovy/graphql/GraphQLTest.groovy | 6 +- ...nterfacesImplementingInterfacesTest.groovy | 87 +++++++++++++------ .../graphql/TypeMismatchErrorTest.groovy | 19 ++-- .../TypeResolutionEnvironmentTest.groovy | 4 +- .../execution/ExecutionStepInfoTest.groovy | 2 - .../schema/GraphQLFieldDefinitionTest.groovy | 20 +++++ .../schema/GraphQLInterfaceTypeTest.groovy | 17 +++- .../schema/GraphQLUnionTypeTest.groovy | 11 ++- .../SchemaPrinterComparatorsTest.groovy | 4 +- .../graphql/schema/SchemaTraverserTest.groovy | 23 +---- .../schema/idl/SchemaPrinterTest.groovy | 34 ++++++-- ...dVisibilitySchemaTransformationTest.groovy | 14 ++- .../validation/TypeAndFieldRuleTest.groovy | 26 +++--- .../TypesImplementInterfacesTest.groovy | 21 ----- .../OverlappingFieldsCanBeMergedTest.groovy | 22 +++-- 17 files changed, 204 insertions(+), 121 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLInterfaceType.java b/src/main/java/graphql/schema/GraphQLInterfaceType.java index f2af03a0de..4f1cf1e51b 100644 --- a/src/main/java/graphql/schema/GraphQLInterfaceType.java +++ b/src/main/java/graphql/schema/GraphQLInterfaceType.java @@ -363,7 +363,13 @@ public Builder clearFields() { return this; } - + /** + * @param typeResolver the type resolver + * + * @return this builder + * + * @deprecated use {@link graphql.schema.GraphQLCodeRegistry.Builder#typeResolver(GraphQLInterfaceType, TypeResolver)} instead + */ @Deprecated @DeprecatedAt("2018-12-03") public Builder typeResolver(TypeResolver typeResolver) { diff --git a/src/main/java/graphql/schema/GraphQLUnionType.java b/src/main/java/graphql/schema/GraphQLUnionType.java index bf39d38401..f2813f9012 100644 --- a/src/main/java/graphql/schema/GraphQLUnionType.java +++ b/src/main/java/graphql/schema/GraphQLUnionType.java @@ -275,6 +275,13 @@ public Builder extensionDefinitions(List extension return this; } + /** + * @param typeResolver the type resolver + * + * @return this builder + * + * @deprecated use {@link graphql.schema.GraphQLCodeRegistry.Builder#typeResolver(GraphQLUnionType, TypeResolver)} instead + */ @Deprecated @DeprecatedAt("2018-12-03") public Builder typeResolver(TypeResolver typeResolver) { diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index c7c2b1ba82..632cec5ec2 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -856,7 +856,6 @@ class GraphQLTest extends Specification { .name("id") .type(Scalars.GraphQLID) } as UnaryOperator) - .typeResolver({ type -> foo }) .build() GraphQLObjectType query = newObject() @@ -869,7 +868,12 @@ class GraphQLTest extends Specification { } as UnaryOperator) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(node, {type -> foo }) + .build() + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) .query(query) .build() diff --git a/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy b/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy index 47af9e7155..b5813c4d0b 100644 --- a/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy +++ b/src/test/groovy/graphql/InterfacesImplementingInterfacesTest.groovy @@ -1,5 +1,6 @@ package graphql +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLSchema @@ -970,14 +971,12 @@ class InterfacesImplementingInterfacesTest extends Specification { def node1Type = newInterface() .name("Node1") .field(newFieldDefinition().name("id1").type(GraphQLString).build()) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) - .build(); + .build() def node2Type = newInterface() .name("Node2") .field(newFieldDefinition().name("id2").type(GraphQLString).build()) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) - .build(); + .build() // references two interfaces: directly and via type ref def resource = newInterface() @@ -986,8 +985,7 @@ class InterfacesImplementingInterfacesTest extends Specification { .field(newFieldDefinition().name("id2").type(GraphQLString).build()) .withInterface(GraphQLTypeReference.typeRef("Node1")) .withInterface(node2Type) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) - .build(); + .build() def image = newObject() .name("Image") .field(newFieldDefinition().name("id1").type(GraphQLString).build()) @@ -1000,7 +998,17 @@ class InterfacesImplementingInterfacesTest extends Specification { .name("Query") .field(newFieldDefinition().name("image").type(image).build()) .build() - def schema = GraphQLSchema.newSchema().query(query).additionalType(node1Type).build(); + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(node1Type, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(node2Type, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(resource, { env -> Assert.assertShouldNeverHappen() }) + .build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .additionalType(node1Type) + .build() when: def printedSchema = new SchemaPrinter().print(schema) @@ -1045,7 +1053,6 @@ type Query { .argument(newArgument().name("arg3").type(GraphQLString)) ) .field(newFieldDefinition().name("field4").type(GraphQLString)) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface2 = newInterface() @@ -1057,7 +1064,6 @@ type Query { .field(newFieldDefinition().name("field2").type(GraphQLInt)) .field(newFieldDefinition().name("field3").type(GraphQLString)) .withInterface(interface1) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def query = newObject() @@ -1065,9 +1071,16 @@ type Query { .field(newFieldDefinition().name("interface2").type(interface2).build()) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(interface1, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface2, { env -> Assert.assertShouldNeverHappen() }) + .build() when: - GraphQLSchema.newSchema().query(query).build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build() then: def error = thrown(InvalidSchemaException) @@ -1094,7 +1107,6 @@ type Query { .argument(newArgument().name("arg3").type(GraphQLString)) ) .field(newFieldDefinition().name("field4").type(GraphQLString)) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface2 = newInterface() @@ -1110,7 +1122,6 @@ type Query { ) .field(newFieldDefinition().name("field4").type(GraphQLString)) .withInterface(interface1) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def query = newObject() @@ -1118,9 +1129,16 @@ type Query { .field(newFieldDefinition().name("interface2").type(interface2).build()) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(interface1, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface2, { env -> Assert.assertShouldNeverHappen() }) + .build() when: - GraphQLSchema.newSchema().query(query).build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build() then: noExceptionThrown() @@ -1131,7 +1149,6 @@ type Query { def interface1 = newInterface() .name("Interface1") .field(newFieldDefinition().name("field1").type(GraphQLString)) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface2 = newInterface() @@ -1139,7 +1156,6 @@ type Query { .field(newFieldDefinition().name("field1").type(GraphQLString)) .field(newFieldDefinition().name("field2").type(GraphQLString)) .withInterface(interface1) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def type = newObject() @@ -1154,9 +1170,16 @@ type Query { .field(newFieldDefinition().name("find").type(type).build()) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(interface1, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface2, { env -> Assert.assertShouldNeverHappen() }) + .build() when: - GraphQLSchema.newSchema().query(query).build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build() then: def error = thrown(InvalidSchemaException) @@ -1169,7 +1192,6 @@ type Query { def interface1 = newInterface() .name("Interface1") .field(newFieldDefinition().name("field1").type(GraphQLString)) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface2 = newInterface() @@ -1177,7 +1199,6 @@ type Query { .field(newFieldDefinition().name("field1").type(GraphQLString)) .field(newFieldDefinition().name("field2").type(GraphQLString)) .withInterface(interface1) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def type = newObject() @@ -1193,9 +1214,16 @@ type Query { .field(newFieldDefinition().name("find").type(type).build()) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(interface1, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface2, { env -> Assert.assertShouldNeverHappen() }) + .build() when: - GraphQLSchema.newSchema().query(query).build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build() then: noExceptionThrown() @@ -1208,7 +1236,6 @@ type Query { .field(newFieldDefinition().name("field1").type(GraphQLString)) .withInterface(GraphQLTypeReference.typeRef("Interface3")) .withInterface(GraphQLTypeReference.typeRef("Interface2")) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface2 = newInterface() @@ -1216,7 +1243,6 @@ type Query { .field(newFieldDefinition().name("field1").type(GraphQLString)) .withInterface(interface1) .withInterface(GraphQLTypeReference.typeRef("Interface3")) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def interface3 = newInterface() @@ -1224,7 +1250,6 @@ type Query { .field(newFieldDefinition().name("field1").type(GraphQLString)) .withInterface(interface1) .withInterface(interface2) - .typeResolver({ env -> Assert.assertShouldNeverHappen() }) .build() def query = newObject() @@ -1232,9 +1257,17 @@ type Query { .field(newFieldDefinition().name("find").type(interface3).build()) .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(interface1, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface2, { env -> Assert.assertShouldNeverHappen() }) + .typeResolver(interface3, { env -> Assert.assertShouldNeverHappen() }) + .build() when: - GraphQLSchema.newSchema().query(query).build() + GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(query) + .build() then: def error = thrown(InvalidSchemaException) @@ -1305,14 +1338,14 @@ type Query { def typeDefinitionRegistry = new SchemaParser().parse(sdl) TypeResolver typeResolver = { env -> - Map obj = env.getObject(); - String id = (String) obj.get("id"); + Map obj = env.getObject() + String id = (String) obj.get("id") GraphQLSchema schema = env.getSchema() if (id == "1") { - return (GraphQLObjectType) schema.getType("Image"); + return (GraphQLObjectType) schema.getType("Image") } else { - return (GraphQLObjectType) schema.getType("File"); + return (GraphQLObjectType) schema.getType("File") } } diff --git a/src/test/groovy/graphql/TypeMismatchErrorTest.groovy b/src/test/groovy/graphql/TypeMismatchErrorTest.groovy index 467c21c57a..ff226aab8b 100644 --- a/src/test/groovy/graphql/TypeMismatchErrorTest.groovy +++ b/src/test/groovy/graphql/TypeMismatchErrorTest.groovy @@ -2,7 +2,6 @@ package graphql import graphql.introspection.Introspection import graphql.schema.GraphQLType -import graphql.schema.TypeResolverProxy import spock.lang.Specification import spock.lang.Unroll @@ -22,14 +21,14 @@ class TypeMismatchErrorTest extends Specification { TypeMismatchError.GraphQLTypeToTypeKindMapping.getTypeKindFromGraphQLType(type) == typeKind where: - type || typeKind - list(Scalars.GraphQLInt) || Introspection.TypeKind.LIST - Scalars.GraphQLInt || Introspection.TypeKind.SCALAR - newObject().name("myObject").fields([]).build() || Introspection.TypeKind.OBJECT - newEnum().name("myEnum").values([]).build() || Introspection.TypeKind.ENUM - newInputObject().name("myInputType").fields([]).build() || Introspection.TypeKind.INPUT_OBJECT - newInterface().name("myInterfaceType").fields([]).typeResolver(new TypeResolverProxy()).build() || Introspection.TypeKind.INTERFACE - nonNull(Scalars.GraphQLInt) || Introspection.TypeKind.NON_NULL - newUnionType().name("myUnion").possibleType(newObject().name("test").build()).build() || Introspection.TypeKind.UNION + type || typeKind + list(Scalars.GraphQLInt) || Introspection.TypeKind.LIST + Scalars.GraphQLInt || Introspection.TypeKind.SCALAR + newObject().name("myObject").fields([]).build() || Introspection.TypeKind.OBJECT + newEnum().name("myEnum").values([]).build() || Introspection.TypeKind.ENUM + newInputObject().name("myInputType").fields([]).build() || Introspection.TypeKind.INPUT_OBJECT + newInterface().name("myInterfaceType").fields([]).build() || Introspection.TypeKind.INTERFACE + nonNull(Scalars.GraphQLInt) || Introspection.TypeKind.NON_NULL + newUnionType().name("myUnion").possibleType(newObject().name("test").build()).build() || Introspection.TypeKind.UNION } } diff --git a/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy b/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy index 70605beb1a..9c773a3504 100644 --- a/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy +++ b/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy @@ -51,7 +51,7 @@ class TypeResolutionEnvironmentTest extends Specification { .field(mergedField(new Field("field"))) .fieldType(interfaceType) .schema(schema) - .context("FooBar") + .context("FooBar") // Retain for test coverage .graphQLContext(graphqlContext) .localContext("LocalContext") .build() @@ -65,7 +65,7 @@ class TypeResolutionEnvironmentTest extends Specification { assert source == "source" assert env.getField().getName() == "field" assert env.getFieldType() == interfaceType - assert env.getContext() == "FooBar" + assert env.getContext() == "FooBar" // Retain for test coverage assert env.getLocalContext() == "LocalContext" assert env.getGraphQLContext() == graphqlContext assert env.getArguments() == [a: "b"] diff --git a/src/test/groovy/graphql/execution/ExecutionStepInfoTest.groovy b/src/test/groovy/graphql/execution/ExecutionStepInfoTest.groovy index e3a7f7a67d..9fc34114a5 100644 --- a/src/test/groovy/graphql/execution/ExecutionStepInfoTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStepInfoTest.groovy @@ -37,7 +37,6 @@ class ExecutionStepInfoTest extends Specification { def interfaceType = GraphQLInterfaceType.newInterface().name("Interface") .field(field1Def) - .typeResolver({ env -> null }) .build() def fieldType = GraphQLObjectType.newObject() @@ -50,7 +49,6 @@ class ExecutionStepInfoTest extends Specification { .field(newFieldDefinition().name("rootField1").type(fieldType)) .build() - def "basic hierarchy"() { given: def rootTypeInfo = newExecutionStepInfo().type(rootType).build() diff --git a/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy b/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy index 3f9e396590..d3c775cc9e 100644 --- a/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLFieldDefinitionTest.groovy @@ -1,15 +1,20 @@ package graphql.schema import graphql.AssertException +import graphql.TestUtil +import graphql.schema.idl.SchemaPrinter import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean import static graphql.Scalars.GraphQLFloat import static graphql.Scalars.GraphQLInt import static graphql.Scalars.GraphQLString +import static graphql.TestUtil.mockArguments +import static graphql.schema.DefaultGraphqlTypeComparatorRegistry.newComparators import static graphql.schema.GraphQLArgument.newArgument import static graphql.schema.GraphQLDirective.newDirective import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition +import static graphql.schema.idl.SchemaPrinter.Options.defaultOptions class GraphQLFieldDefinitionTest extends Specification { @@ -77,4 +82,19 @@ class GraphQLFieldDefinitionTest extends Specification { transformedField.getDirective("directive2") != null transformedField.getDirective("directive3") != null } + + def "test deprecated argument builder for list"() { + given: + def field = newFieldDefinition().name("field").type(GraphQLInt).argument(mockArguments("a", "bb")).build() // Retain for test coverage + + when: + def registry = newComparators() + .addComparator({ it.parentType(GraphQLFieldDefinition.class).elementType(GraphQLArgument.class) }, GraphQLArgument.class, TestUtil.byGreatestLength) + .build() + def options = defaultOptions().setComparators(registry) + def printer = new SchemaPrinter(options) + + then: + printer.argsString(GraphQLFieldDefinition.class, field.arguments) == '''(bb: Int, a: Int)''' + } } diff --git a/src/test/groovy/graphql/schema/GraphQLInterfaceTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLInterfaceTypeTest.groovy index af67996118..b6c4273ae3 100644 --- a/src/test/groovy/graphql/schema/GraphQLInterfaceTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLInterfaceTypeTest.groovy @@ -24,7 +24,6 @@ class GraphQLInterfaceTypeTest extends Specification { newFieldDefinition().name("NAME").type(GraphQLString).build(), newFieldDefinition().name("NAME").type(GraphQLInt).build() ]) - .typeResolver(new TypeResolverProxy()) .build() then: interfaceType.getName() == "TestInterfaceType" @@ -37,7 +36,6 @@ class GraphQLInterfaceTypeTest extends Specification { .description("StartingDescription") .field(newFieldDefinition().name("Str").type(GraphQLString)) .field(newFieldDefinition().name("Int").type(GraphQLInt)) - .typeResolver(new TypeResolverProxy()) .build() when: @@ -107,4 +105,19 @@ class GraphQLInterfaceTypeTest extends Specification { then: noExceptionThrown() } + + def "deprecated typeResolver builder works"() { + when: + def interfaceType = newInterface().name("TestInterfaceType") + .description("description") + .fields([ + newFieldDefinition().name("NAME").type(GraphQLString).build(), + newFieldDefinition().name("NAME").type(GraphQLInt).build() + ]) + .typeResolver(new TypeResolverProxy()) // Retain for test coverage + .build() + then: + interfaceType.getName() == "TestInterfaceType" + interfaceType.getFieldDefinition("NAME").getType() == GraphQLInt + } } diff --git a/src/test/groovy/graphql/schema/GraphQLUnionTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLUnionTypeTest.groovy index e537a8766b..6524d135da 100644 --- a/src/test/groovy/graphql/schema/GraphQLUnionTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLUnionTypeTest.groovy @@ -14,7 +14,6 @@ class GraphQLUnionTypeTest extends Specification { when: newUnionType() .name("TestUnionType") - .typeResolver(new TypeResolverProxy()) .build() then: thrown(AssertException) @@ -38,7 +37,6 @@ class GraphQLUnionTypeTest extends Specification { .description("StartingDescription") .possibleType(objType1) .possibleType(objType2) - .typeResolver(new TypeResolverProxy()) .build() when: @@ -65,4 +63,13 @@ class GraphQLUnionTypeTest extends Specification { transformedUnion.isPossibleType(objType3) } + def "deprecated typeResolver builder works"() { + when: + newUnionType() + .name("TestUnionType") + .typeResolver(new TypeResolverProxy()) // Retain for test coverage + .build() + then: + thrown(AssertException) + } } diff --git a/src/test/groovy/graphql/schema/SchemaPrinterComparatorsTest.groovy b/src/test/groovy/graphql/schema/SchemaPrinterComparatorsTest.groovy index 6ddb0bc04c..f2a12e3b7e 100644 --- a/src/test/groovy/graphql/schema/SchemaPrinterComparatorsTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaPrinterComparatorsTest.groovy @@ -534,7 +534,7 @@ scalar TestScalar @bb(bb : 0, a : 0) @a(bb : 0, a : 0) def "argsString uses most specific registered comparators"() { given: - def field = newFieldDefinition().name("field").type(GraphQLInt).argument(mockArguments("a", "bb")).build() + def field = newFieldDefinition().name("field").type(GraphQLInt).arguments(mockArguments("a", "bb")).build() when: def registry = newComparators() @@ -549,7 +549,7 @@ scalar TestScalar @bb(bb : 0, a : 0) @a(bb : 0, a : 0) def "argsString uses least specific registered comparators"() { given: - def field = newFieldDefinition().name("field").type(GraphQLInt).argument(mockArguments("a", "bb")).build() + def field = newFieldDefinition().name("field").type(GraphQLInt).arguments(mockArguments("a", "bb")).build() when: def registry = newComparators() diff --git a/src/test/groovy/graphql/schema/SchemaTraverserTest.groovy b/src/test/groovy/graphql/schema/SchemaTraverserTest.groovy index ea6deb208e..889e7e2198 100644 --- a/src/test/groovy/graphql/schema/SchemaTraverserTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTraverserTest.groovy @@ -1,7 +1,6 @@ package graphql.schema import graphql.Scalars -import graphql.TypeResolutionEnvironment import graphql.util.TraversalControl import graphql.util.TraverserContext import spock.lang.Specification @@ -117,7 +116,6 @@ class SchemaTraverserTest extends Specification { .name("bar") .type(Scalars.GraphQLString) .build()) - .typeResolver(NOOP_RESOLVER) .build()) then: visitor.getStack() == ["interface: foo", "fallback: foo", @@ -155,7 +153,6 @@ class SchemaTraverserTest extends Specification { .build()) .withInterface(GraphQLInterfaceType.newInterface() .name("bar") - .typeResolver(NOOP_RESOLVER) .build()) .build()) then: @@ -181,7 +178,6 @@ class SchemaTraverserTest extends Specification { .name("foo") .possibleType(GraphQLObjectType.newObject().name("dummy").build()) .possibleType(typeRef("dummyRef")) - .typeResolver(NOOP_RESOLVER) .build()) then: visitor.getStack() == ["union: foo", "fallback: foo", @@ -193,21 +189,21 @@ class SchemaTraverserTest extends Specification { when: def visitor = new GraphQLTestingVisitor() def coercing = new Coercing() { - private static final String TEST_ONLY = "For testing only"; + private static final String TEST_ONLY = "For testing only" @Override Object serialize(Object dataFetcherResult) throws CoercingSerializeException { - throw new UnsupportedOperationException(TEST_ONLY); + throw new UnsupportedOperationException(TEST_ONLY) } @Override Object parseValue(Object input) throws CoercingParseValueException { - throw new UnsupportedOperationException(TEST_ONLY); + throw new UnsupportedOperationException(TEST_ONLY) } @Override Object parseLiteral(Object input) throws CoercingParseLiteralException { - throw new UnsupportedOperationException(TEST_ONLY); + throw new UnsupportedOperationException(TEST_ONLY) } } def scalarType = GraphQLScalarType.newScalar() @@ -283,7 +279,6 @@ class SchemaTraverserTest extends Specification { def visitor = new GraphQLTestingVisitor() def interfaceType = GraphQLInterfaceType.newInterface() .name("foo") - .typeResolver(NOOP_RESOLVER) .withDirective(GraphQLDirective.newDirective() .name("bar")) .withAppliedDirective(GraphQLAppliedDirective.newDirective() @@ -302,7 +297,6 @@ class SchemaTraverserTest extends Specification { def unionType = GraphQLUnionType.newUnionType() .name("foo") .possibleType(GraphQLObjectType.newObject().name("dummy").build()) - .typeResolver(NOOP_RESOLVER) .withDirective(GraphQLDirective.newDirective() .name("bar")) .build() @@ -388,15 +382,6 @@ class SchemaTraverserTest extends Specification { } - - def NOOP_RESOLVER = new TypeResolver() { - @Override - GraphQLObjectType getType(TypeResolutionEnvironment env) { - return null - } - } - - class GraphQLTestingVisitor extends GraphQLTypeVisitorStub { def stack = [] diff --git a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy index 513c214445..e946a06278 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy @@ -428,12 +428,19 @@ enum Enum { .name("Union") .description("About union") .possibleType(possibleType) - .typeResolver({ it -> null }) .build() GraphQLFieldDefinition fieldDefinition2 = newFieldDefinition() .name("field").type(unionType).build() + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(unionType, { it -> null }) + .build() def queryType = newObject().name("Query").field(fieldDefinition2).build() - def schema = GraphQLSchema.newSchema().query(queryType).build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(queryType) + .build() + when: def result = new SchemaPrinter(noDirectivesOption).print(schema) @@ -463,12 +470,19 @@ type Query { .name("Union") .possibleType(possibleType1) .possibleType(possibleType2) - .typeResolver({ it -> null }) .build() GraphQLFieldDefinition fieldDefinition2 = newFieldDefinition() .name("field").type(unionType).build() + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(unionType, { it -> null }) + .build() def queryType = newObject().name("Query").field(fieldDefinition2).build() - def schema = GraphQLSchema.newSchema().query(queryType).build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(queryType) + .build() + when: def result = new SchemaPrinter(noDirectivesOption).print(schema) @@ -526,12 +540,19 @@ input Input { .name("Interface") .description("about interface") .field(newFieldDefinition().name("field").description("about field").type(GraphQLString).build()) - .typeResolver({ it -> null }) .build() GraphQLFieldDefinition fieldDefinition = newFieldDefinition() .name("field").type(graphQLInterfaceType).build() + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(graphQLInterfaceType, { it -> null }) + .build() def queryType = newObject().name("Query").field(fieldDefinition).build() - def schema = GraphQLSchema.newSchema().query(queryType).build() + def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(queryType) + .build() + when: def result = new SchemaPrinter(noDirectivesOption).print(schema) @@ -852,7 +873,6 @@ type Query { ''' } - def idlWithDirectives() { return """ directive @interfaceFieldDirective on FIELD_DEFINITION diff --git a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy index a6f82018a2..4995ed029a 100644 --- a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy +++ b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy @@ -2,7 +2,7 @@ package graphql.schema.transform import graphql.Scalars import graphql.TestUtil -import graphql.introspection.Introspection +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLInputObjectType import graphql.schema.GraphQLObjectType @@ -957,10 +957,14 @@ class FieldVisibilitySchemaTransformationTest extends Specification { def secretData = newInterface() .name("SuperSecretCustomerData") .field(newFieldDefinition().name("id").type(Scalars.GraphQLString).build()) - .typeResolver(Mock(TypeResolver)) + .build() + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(secretData, Mock(TypeResolver)) .build() def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(query) .additionalType(billingStatus) .additionalType(account) @@ -1003,10 +1007,14 @@ class FieldVisibilitySchemaTransformationTest extends Specification { def secretData = newInterface() .name("SuperSecretCustomerData") .field(newFieldDefinition().name("id").type(Scalars.GraphQLString).build()) - .typeResolver(Mock(TypeResolver)) + .build() + + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(secretData, Mock(TypeResolver)) .build() def schema = GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) .query(query) .additionalType(billingStatus) .additionalType(account) diff --git a/src/test/groovy/graphql/schema/validation/TypeAndFieldRuleTest.groovy b/src/test/groovy/graphql/schema/validation/TypeAndFieldRuleTest.groovy index c516fcdcfb..8c3f4fc2a3 100644 --- a/src/test/groovy/graphql/schema/validation/TypeAndFieldRuleTest.groovy +++ b/src/test/groovy/graphql/schema/validation/TypeAndFieldRuleTest.groovy @@ -1,6 +1,7 @@ package graphql.schema.validation import graphql.TestUtil +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLTypeReference import graphql.schema.TypeResolverProxy @@ -10,7 +11,6 @@ import static graphql.schema.GraphQLUnionType.newUnionType class TypeAndFieldRuleTest extends Specification { - def "type must define one or more fields."() { when: def sdl = ''' @@ -55,7 +55,6 @@ class TypeAndFieldRuleTest extends Specification { e.message == "invalid schema:\n\"InputType\" must define one or more fields." } - def "field name must not begin with \"__\""() { when: def sdl = ''' @@ -93,8 +92,6 @@ class TypeAndFieldRuleTest extends Specification { e.message == "invalid schema:\n\"Interface\" must define one or more fields." } - - def "union member types must be object types"() { def sdl = ''' type Query { dummy: String } @@ -108,7 +105,10 @@ class TypeAndFieldRuleTest extends Specification { } ''' when: - def graphQLSchema = TestUtil.schema(sdl) + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver("Interface", { null }) + .build() + def graphQLSchema = TestUtil.schema(sdl).transform({it.codeRegistry(codeRegistry)}) // this is a little convoluted, since this rule is repeated in the schemaChecker // we add the invalid union after schema creation so we can cover the validation from @@ -116,10 +116,11 @@ class TypeAndFieldRuleTest extends Specification { def unionType = newUnionType().name("unionWithNonObjectTypes") .possibleType(graphQLSchema.getObjectType("Object")) .possibleType(GraphQLTypeReference.typeRef("Interface")) - .typeResolver(new TypeResolverProxy()) .build() - - graphQLSchema.transform({ schema -> schema.additionalType(unionType) }) + codeRegistry = codeRegistry.transform({it.typeResolver(unionType, new TypeResolverProxy())}) + graphQLSchema.transform({ schema -> schema + .additionalType(unionType) + .codeRegistry(codeRegistry)}) then: InvalidSchemaException e = thrown(InvalidSchemaException) @@ -149,10 +150,15 @@ class TypeAndFieldRuleTest extends Specification { def unionType = newUnionType().name("unionWithNonObjectTypes") .possibleType(graphQLSchema.getObjectType("Object")) .possibleType(stubObjectType) - .typeResolver(new TypeResolverProxy()) .build() - graphQLSchema.transform({ schema -> schema.additionalType(unionType) }) + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(unionType, new TypeResolverProxy()) + .build() + + graphQLSchema.transform({ schema -> schema + .additionalType(unionType) + .codeRegistry(codeRegistry) }) then: InvalidSchemaException e = thrown(InvalidSchemaException) diff --git a/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy b/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy index bf134d063c..c504393cf2 100644 --- a/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy +++ b/src/test/groovy/graphql/schema/validation/TypesImplementInterfacesTest.groovy @@ -1,9 +1,7 @@ package graphql.schema.validation -import graphql.TypeResolutionEnvironment import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLObjectType -import graphql.schema.TypeResolver import spock.lang.Specification import static SchemaValidationErrorType.ObjectDoesNotImplementItsInterfaces @@ -20,13 +18,6 @@ import static graphql.schema.GraphQLUnionType.newUnionType class TypesImplementInterfacesTest extends Specification { - TypeResolver typeResolver = new TypeResolver() { - @Override - GraphQLObjectType getType(TypeResolutionEnvironment env) { - null - } - } - GraphQLInterfaceType InterfaceType = newInterface() .name("Interface") @@ -47,7 +38,6 @@ class TypesImplementInterfacesTest extends Specification { .argument(newArgument().name("arg2").type(GraphQLInt)) .argument(newArgument().name("arg3").type(GraphQLBoolean)) ) - .typeResolver(typeResolver) .build() def "objects implement interfaces"() { @@ -102,7 +92,6 @@ class TypesImplementInterfacesTest extends Specification { def person = newInterface() .name("Person") .field(newFieldDefinition().name("name").type(GraphQLString).build()) - .typeResolver({}) .build() def actor = newObject() @@ -119,7 +108,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType interfaceType = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(person).build()) - .typeResolver({}) .build() GraphQLObjectType goodImpl = newObject() @@ -151,7 +139,6 @@ class TypesImplementInterfacesTest extends Specification { def person = newInterface() .name("Person") .field(newFieldDefinition().name("name").type(GraphQLString).build()) - .typeResolver({}) .build() def actor = newObject() @@ -168,7 +155,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType interfaceType = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(list(person)).build()) - .typeResolver({}) .build() GraphQLObjectType goodImpl = newObject() @@ -200,7 +186,6 @@ class TypesImplementInterfacesTest extends Specification { def person = newInterface() .name("Person") .field(newFieldDefinition().name("name").type(GraphQLString).build()) - .typeResolver({}) .build() def actor = newInterface() @@ -217,7 +202,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType interfaceType = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(list(person)).build()) - .typeResolver({}) .build() GraphQLObjectType goodImpl = newObject() @@ -260,7 +244,6 @@ class TypesImplementInterfacesTest extends Specification { .name("Person") .possibleType(actor) .possibleType(director) - .typeResolver({}) .build() def prop = newObject() @@ -271,7 +254,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType interfaceType = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(person).build()) - .typeResolver({}) .build() GraphQLObjectType goodImpl = newObject() @@ -303,7 +285,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType interfaceType = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(GraphQLString).build()) - .typeResolver({}) .build() GraphQLObjectType goodImpl = newObject() @@ -336,7 +317,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType memberInterface = newInterface() .name("TestMemberInterface") .field(newFieldDefinition().name("field").type(GraphQLString).build()) - .typeResolver({}) .build() GraphQLObjectType memberInterfaceImpl = newObject() @@ -348,7 +328,6 @@ class TypesImplementInterfacesTest extends Specification { GraphQLInterfaceType testInterface = newInterface() .name("TestInterface") .field(newFieldDefinition().name("field").type(nonNull(memberInterface)).build()) - .typeResolver({}) .build() GraphQLObjectType testInterfaceImpl = newObject() diff --git a/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy b/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy index deedfbed77..f9586b337f 100644 --- a/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy +++ b/src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy @@ -1,14 +1,11 @@ package graphql.validation.rules - -import graphql.TypeResolutionEnvironment import graphql.i18n.I18n import graphql.language.Document import graphql.language.SourceLocation import graphql.parser.Parser -import graphql.schema.GraphQLObjectType +import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLSchema -import graphql.schema.TypeResolver import graphql.validation.LanguageTraversal import graphql.validation.RulesVisitor import graphql.validation.ValidationContext @@ -79,7 +76,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification { errorCollector.getErrors()[0].locations == [new SourceLocation(3, 17), new SourceLocation(4, 17)] } - GraphQLSchema unionSchema() { + static GraphQLSchema unionSchema() { def StringBox = newObject().name("StringBox") .field(newFieldDefinition().name("scalar").type(GraphQLString)) .build() @@ -102,17 +99,18 @@ class OverlappingFieldsCanBeMergedTest extends Specification { def BoxUnion = newUnionType() .name("BoxUnion") .possibleTypes(StringBox, IntBox, NonNullStringBox1, NonNullStringBox2, ListStringBox1) - .typeResolver(new TypeResolver() { - @Override - GraphQLObjectType getType(TypeResolutionEnvironment env) { - return null - } - }) + .build() + def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver(BoxUnion, { env -> null }) .build() def QueryRoot = newObject() .name("QueryRoot") .field(newFieldDefinition().name("boxUnion").type(BoxUnion)).build() - return GraphQLSchema.newSchema().query(QueryRoot).build() + + return GraphQLSchema.newSchema() + .codeRegistry(codeRegistry) + .query(QueryRoot) + .build() } def 'conflicting scalar return types'() { From 75f78dc8ec9a79bf4d1f4127e042bca3fe30375a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 13:37:47 +1000 Subject: [PATCH 125/294] Use DataLoaderFactory builder instead --- .../DataLoaderDispatcherInstrumentationTest.groovy | 9 ++++----- .../schema/DataFetchingEnvironmentImplTest.groovy | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy index 23d00ce90f..fd8a29980e 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy @@ -14,7 +14,7 @@ import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.schema.DataFetcher import org.dataloader.BatchLoader -import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification import spock.lang.Unroll @@ -39,7 +39,6 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { } } - def query = """ query { hero { @@ -104,7 +103,7 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { super.dispatchAll() } } - def dataLoader = DataLoader.newDataLoader(new BatchLoader() { + def dataLoader = DataLoaderFactory.newDataLoader(new BatchLoader() { @Override CompletionStage load(List keys) { return CompletableFuture.completedFuture(keys) @@ -127,7 +126,7 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { def starWarsWiring = new StarWarsDataLoaderWiring() - DataLoaderRegistry startingDataLoaderRegistry = new DataLoaderRegistry(); + DataLoaderRegistry startingDataLoaderRegistry = new DataLoaderRegistry() def enhancedDataLoaderRegistry = starWarsWiring.newDataLoaderRegistry() def dlInstrumentation = new DataLoaderDispatcherInstrumentation() @@ -274,7 +273,7 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { BatchLoader batchLoader = { keys -> CompletableFuture.completedFuture(keys) } DataFetcher df = { env -> - def dataLoader = env.getDataLoaderRegistry().computeIfAbsent("key", { key -> DataLoader.newDataLoader(batchLoader) }) + def dataLoader = env.getDataLoaderRegistry().computeIfAbsent("key", { key -> DataLoaderFactory.newDataLoader(batchLoader) }) return dataLoader.load("working as expected") } diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index f23475e90d..2830419999 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -11,7 +11,7 @@ import graphql.language.OperationDefinition import graphql.language.StringValue import graphql.language.TypeName import org.dataloader.BatchLoader -import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -27,7 +27,7 @@ class DataFetchingEnvironmentImplTest extends Specification { def frag = FragmentDefinition.newFragmentDefinition().name("frag").typeCondition(new TypeName("t")).build() - def dataLoader = DataLoader.newDataLoader({ keys -> CompletableFuture.completedFuture(keys) } as BatchLoader) + def dataLoader = DataLoaderFactory.newDataLoader({ keys -> CompletableFuture.completedFuture(keys) } as BatchLoader) def operationDefinition = new OperationDefinition("q") def document = toDocument("{ f }") def executionId = ExecutionId.from("123") From a40eab566be7fda7773dd765f5060aeea1ca208f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 13:56:55 +1000 Subject: [PATCH 126/294] Replace staticValue with datafetcher --- src/test/groovy/graphql/GraphQLTest.groovy | 52 ++++++++++++++-------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 632cec5ec2..445ba1cc56 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -64,13 +64,19 @@ class GraphQLTest extends Specification { GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition() .name("hello") .type(GraphQLString) - .staticValue("world") - GraphQLSchema schema = newSchema().query( - newObject() + FieldCoordinates fieldCoordinates = FieldCoordinates.coordinates("RootQueryType", "hello") + DataFetcher dataFetcher = { env -> "world" } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fieldCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() .name("RootQueryType") .field(fieldDefinition) - .build() - ).build() + .build()) + .build() schema } @@ -83,7 +89,6 @@ class GraphQLTest extends Specification { then: result == [hello: 'world'] - } def "query with sub-fields"() { @@ -103,14 +108,20 @@ class GraphQLTest extends Specification { GraphQLFieldDefinition.Builder simpsonField = newFieldDefinition() .name("simpson") .type(heroType) - .staticValue([id: '123', name: 'homer']) - GraphQLSchema graphQLSchema = newSchema().query( - newObject() + FieldCoordinates fieldCoordinates = FieldCoordinates.coordinates("RootQueryType", "simpson") + DataFetcher dataFetcher = { env -> [id: '123', name: 'homer'] } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fieldCoordinates, dataFetcher) + .build() + + GraphQLSchema graphQLSchema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() .name("RootQueryType") .field(simpsonField) - .build() - ).build() + .build()) + .build() when: def result = GraphQL.newGraphQL(graphQLSchema).build().execute('{ simpson { id, name } }').data @@ -125,13 +136,20 @@ class GraphQLTest extends Specification { .name("hello") .type(GraphQLString) .argument(newArgument().name("arg").type(GraphQLString)) - .staticValue("world") - GraphQLSchema schema = newSchema().query( - newObject() + + FieldCoordinates fieldCoordinates = FieldCoordinates.coordinates("RootQueryType", "hello") + DataFetcher dataFetcher = { env -> "hello" } + GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .dataFetcher(fieldCoordinates, dataFetcher) + .build() + + GraphQLSchema schema = newSchema() + .codeRegistry(codeRegistry) + .query(newObject() .name("RootQueryType") .field(fieldDefinition) - .build() - ).build() + .build()) + .build() when: def errors = GraphQL.newGraphQL(schema).build().execute('{ hello(arg:11) }').errors @@ -696,7 +714,6 @@ class GraphQLTest extends Specification { } - def "execution input passing builder"() { given: GraphQLSchema schema = simpleSchema() @@ -714,7 +731,6 @@ class GraphQLTest extends Specification { GraphQLSchema schema = simpleSchema() when: - def builderFunction = { it.query('{hello}') } as UnaryOperator def result = GraphQL.newGraphQL(schema).build().execute(builderFunction).data From 54f7f13e9d579e9af613b1448939f6428dd433ff Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 14:15:27 +1000 Subject: [PATCH 127/294] Update directive tests to use applied directives --- .../introspection/IntrospectionTest.groovy | 2 +- .../SchemaGeneratorDirectiveHelperTest.groovy | 27 +++++++++---------- .../schema/idl/SchemaGeneratorTest.groovy | 12 ++++----- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 0590ae766e..18f259cbed 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -421,7 +421,7 @@ class IntrospectionTest extends Specification { .name("inputField") .type(GraphQLString)) .build()) - .defaultValue(new FooBar()) + .defaultValue(new FooBar()) // Retain for test coverage. There is no alternative method that sets an internal value. ) ) ) diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy index e27d618db5..f021c6542c 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy @@ -11,9 +11,9 @@ import graphql.schema.CoercingSerializeException import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment import graphql.schema.FieldCoordinates +import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLArgument import graphql.schema.GraphQLCodeRegistry -import graphql.schema.GraphQLDirective import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLEnumValueDefinition import graphql.schema.GraphQLFieldDefinition @@ -110,14 +110,12 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { indirectInputField1 : InputType @inputFieldDirective(target : "indirectInputField1") } - enum EnumType @enumDirective(target:"EnumType") { enumVal1 @enumValueDirective(target : "enumVal1") enumVal2 @enumValueDirective(target : "enumVal2") } scalar ScalarType @scalarDirective(target:"ScalarType") - ''' //`this contains the name of the element that was asked to be directive wired @@ -142,8 +140,9 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { fieldsContainers[name] = environment.getFieldsContainer()?.getName() fieldDefinitions[name] = environment.getFieldDefinition()?.getName() - GraphQLDirective directive = environment.getDirective() - def arg = directive.getArgument("target") + GraphQLAppliedDirective appliedDirective = environment.getAppliedDirective() + def arg = appliedDirective.getArgument("target") + String target = ValuesResolver.valueToInternalValue(arg.getArgumentValue(), arg.getType(), GraphQLContext.getDefault(), Locale.getDefault()) assert name == target, " The target $target is not equal to the object name $name" return element @@ -328,7 +327,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { // we use the non shortcut path to the data fetcher here so prove it still works def fetcher = directiveEnv.getCodeRegistry().getDataFetcher(directiveEnv.fieldsContainer, field) def newFetcher = wrapDataFetcher(fetcher, { dfEnv, value -> - def directiveName = directiveEnv.directive.name + def directiveName = directiveEnv.appliedDirective.name if (directiveName == "uppercase") { return String.valueOf(value).toUpperCase() } else if (directiveName == "lowercase") { @@ -620,7 +619,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { def namedWiring = new SchemaDirectiveWiring() { @Override GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment environment) { - GraphQLDirective directive = environment.getDirective() + GraphQLAppliedDirective directive = environment.getAppliedDirective() DataFetcher existingFetcher = environment.getFieldDataFetcher() DataFetcher newDF = new DataFetcher() { @@ -684,7 +683,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { @Override GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { generalCount++ - def directiveNames = env.getDirectives().values().collect { d -> d.getName() }.sort() + def directiveNames = env.getAppliedDirectives().values().collect { d -> d.getName() }.sort() assert directiveNames == ["factoryDirective", "generalDirective", "namedDirective1", "namedDirective2"] return env.getFieldDefinition() } @@ -694,7 +693,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { @Override GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { factoryCount++ - def directiveNames = env.getDirectives().values().collect { d -> d.getName() }.sort() + def directiveNames = env.getAppliedDirectives().values().collect { d -> d.getName() }.sort() assert directiveNames == ["factoryDirective", "generalDirective", "namedDirective1", "namedDirective2"] return env.getFieldDefinition() } @@ -716,10 +715,10 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { @Override GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { namedCount++ - def directiveNames = env.getDirectives().values().collect { d -> d.getName() }.sort() + def directiveNames = env.getAppliedDirectives().values().collect { d -> d.getName() }.sort() assert directiveNames == ["factoryDirective", "generalDirective", "namedDirective1", "namedDirective2"] - assert env.getDirective("factoryDirective") != null + assert env.getAppliedDirective("factoryDirective") != null assert env.containsDirective("factoryDirective") return env.getFieldDefinition() } @@ -776,11 +775,11 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { argCount++ def arg = env.getElement() if (arg.getName() == "arg1") { - assert env.getDirectives().keySet().sort() == ["argDirective1", "argDirective2"] + assert env.getAppliedDirectives().keySet().sort() == ["argDirective1", "argDirective2"] assert env.getAppliedDirectives().keySet().sort() == ["argDirective1", "argDirective2"] } if (arg.getName() == "arg2") { - assert env.getDirectives().keySet().sort() == ["argDirective3"] + assert env.getAppliedDirectives().keySet().sort() == ["argDirective3"] assert env.getAppliedDirectives().keySet().sort() == ["argDirective3"] } def fieldDef = env.getFieldDefinition() @@ -794,7 +793,7 @@ class SchemaGeneratorDirectiveHelperTest extends Specification { GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { fieldCount++ - assert env.getDirectives().keySet().sort() == ["fieldDirective"] + assert env.getAppliedDirectives().keySet().sort() == ["fieldDirective"] def fieldDef = env.getFieldDefinition() assert fieldDef.getDirectives().collect({ d -> d.getName() }) == ["fieldDirective"] diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 010a9aee77..11db18bf21 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -1366,7 +1366,7 @@ class SchemaGeneratorTest extends Specification { expect: - container.getDirective(directiveName) != null + container.getAppliedDirective(directiveName) != null if (container instanceof GraphQLEnumType) { def evd = ((GraphQLEnumType) container).getValue("X").getDirective("EnumValueDirective") @@ -2027,16 +2027,16 @@ class SchemaGeneratorTest extends Specification { schema.getMutationType().name == 'Mutation' when: - def directives = schema.getSchemaDirectives() + def directives = schema.getSchemaDirectives() // Retain for test coverage then: directives.size() == 3 - schema.getSchemaDirective("sd1") != null - schema.getSchemaDirective("sd2") != null - schema.getSchemaDirective("sd3") != null + schema.getSchemaDirective("sd1") != null // Retain for test coverage + schema.getSchemaDirective("sd2") != null // Retain for test coverage + schema.getSchemaDirective("sd3") != null // Retain for test coverage when: - def directivesMap = schema.getSchemaDirectiveByName() + def directivesMap = schema.getSchemaDirectiveByName() // Retain for test coverage then: directives.size() == 3 directivesMap["sd1"] != null From bf628cad9ba7c924a97ea1c9b9c8e04a4a426feb Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 15:08:13 +1000 Subject: [PATCH 128/294] Update tests to use correct directive methods --- .../schema/idl/SchemaGeneratorTest.groovy | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 11db18bf21..2d0e4480fa 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -8,6 +8,7 @@ import graphql.schema.DataFetcher import graphql.schema.DataFetcherFactory import graphql.schema.DataFetcherFactoryEnvironment import graphql.schema.DataFetchingEnvironment +import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLArgument import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLDirective @@ -27,7 +28,6 @@ import graphql.schema.GraphQLType import graphql.schema.GraphQLTypeUtil import graphql.schema.GraphQLUnionType import graphql.schema.GraphqlTypeComparatorRegistry -import graphql.schema.PropertyDataFetcher import graphql.schema.idl.errors.NotAnInputTypeError import graphql.schema.idl.errors.NotAnOutputTypeError import graphql.schema.idl.errors.SchemaProblem @@ -1310,8 +1310,7 @@ class SchemaGeneratorTest extends Specification { def fieldDirective2 = field2.getDirectives()[0] fieldDirective2.getName() == "fieldDirective2" - - def directive = type.getDirectives()[3] as GraphQLDirective + def directive = type.getAppliedDirectives()[3] as GraphQLAppliedDirective directive.name == "directiveWithArgs" directive.arguments.size() == 5 @@ -1634,13 +1633,14 @@ class SchemaGeneratorTest extends Specification { argInt.getDirective("thirdDirective") != null def intDirective = argInt.getDirective("intDirective") - intDirective.name == "intDirective" - intDirective.arguments.size() == 1 - def directiveArg = intDirective.getArgument("inception") + def intAppliedDirective = argInt.getAppliedDirective("intDirective") + intAppliedDirective.name == "intDirective" + intAppliedDirective.arguments.size() == 1 + def directiveArg = intAppliedDirective.getArgument("inception") directiveArg.name == "inception" directiveArg.type == GraphQLBoolean printAst(directiveArg.argumentValue.value as Node) == "true" - directiveArg.argumentDefaultValue.value == null + intDirective.getArgument("inception").argumentDefaultValue.value == null } def "directives definitions can be made"() { @@ -1702,13 +1702,15 @@ class SchemaGeneratorTest extends Specification { then: def directiveTest1 = schema.getDirective("test1") GraphQLNonNull.nonNull(GraphQLBoolean).isEqualTo(directiveTest1.getArgument("include").type) - directiveTest1.getArgument("include").argumentValue.value == null + directiveTest1.getArgument("include").argumentDefaultValue.value == null + def appliedDirective1 = schema.getObjectType("Query").getFieldDefinition("f1").getAppliedDirective("test1") + printAst(appliedDirective1.getArgument("include").argumentValue.value as Node) == "false" def directiveTest2 = schema.getDirective("test2") GraphQLNonNull.nonNull(GraphQLBoolean).isEqualTo(directiveTest2.getArgument("include").type) - printAst(directiveTest2.getArgument("include").argumentValue.value as Node) == "true" printAst(directiveTest2.getArgument("include").argumentDefaultValue.value as Node) == "true" - + def appliedDirective2 = schema.getObjectType("Query").getFieldDefinition("f2").getAppliedDirective("test2") + printAst(appliedDirective2.getArgument("include").argumentValue.value as Node) == "true" } def "missing directive arguments are transferred as are default values"() { @@ -1733,16 +1735,17 @@ class SchemaGeneratorTest extends Specification { then: def directive = schema.getObjectType("Query").getFieldDefinition("f").getDirective("testDirective") directive.getArgument("knownArg1").type == GraphQLString - printAst(directive.getArgument("knownArg1").argumentValue.value as Node) == '"overrideVal1"' printAst(directive.getArgument("knownArg1").argumentDefaultValue.value as Node) == '"defaultValue1"' + def appliedDirective = schema.getObjectType("Query").getFieldDefinition("f").getAppliedDirective("testDirective") + printAst(appliedDirective.getArgument("knownArg1").argumentValue.value as Node) == '"overrideVal1"' directive.getArgument("knownArg2").type == GraphQLInt - printAst(directive.getArgument("knownArg2").argumentValue.value as Node) == "666" printAst(directive.getArgument("knownArg2").argumentDefaultValue.value as Node) == "666" + printAst(appliedDirective.getArgument("knownArg2").argumentValue.value as Node) == "666" directive.getArgument("knownArg3").type == GraphQLString - directive.getArgument("knownArg3").argumentValue.value == null directive.getArgument("knownArg3").argumentDefaultValue.value == null + appliedDirective.getArgument("knownArg3").argumentValue.value == null } def "deprecated directive is implicit"() { @@ -1765,25 +1768,27 @@ class SchemaGeneratorTest extends Specification { f1.getDeprecationReason() == "No longer supported" // spec default text def directive = f1.getDirective("deprecated") - directive.name == "deprecated" - directive.getArgument("reason").type == GraphQLString - printAst(directive.getArgument("reason").argumentValue.value as Node) == '"No longer supported"' printAst(directive.getArgument("reason").argumentDefaultValue.value as Node) == '"No longer supported"' directive.validLocations().collect { it.name() } == [Introspection.DirectiveLocation.FIELD_DEFINITION.name()] + def appliedDirective = f1.getAppliedDirective("deprecated") + appliedDirective.name == "deprecated" + appliedDirective.getArgument("reason").type == GraphQLString + printAst(appliedDirective.getArgument("reason").argumentValue.value as Node) == '"No longer supported"' + when: def f2 = schema.getObjectType("Query").getFieldDefinition("f2") then: f2.getDeprecationReason() == "Just because" + def appliedDirective2 = f2.getAppliedDirective("deprecated") + appliedDirective2.name == "deprecated" + appliedDirective2.getArgument("reason").type == GraphQLString + printAst(appliedDirective2.getArgument("reason").argumentValue.value as Node) == '"Just because"' def directive2 = f2.getDirective("deprecated") - directive2.name == "deprecated" - directive2.getArgument("reason").type == GraphQLString - printAst(directive2.getArgument("reason").argumentValue.value as Node) == '"Just because"' printAst(directive2.getArgument("reason").argumentDefaultValue.value as Node) == '"No longer supported"' directive2.validLocations().collect { it.name() } == [Introspection.DirectiveLocation.FIELD_DEFINITION.name()] - } def "does not break for circular references to interfaces"() { From 0a2f2952cdc1c2ac7c7202b51ca6a35692fcfe3b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 15:19:04 +1000 Subject: [PATCH 129/294] Move deprecated argument test --- src/test/groovy/graphql/GraphQLTest.groovy | 24 ---------------- .../graphql/schema/GraphQLArgumentTest.groovy | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 445ba1cc56..d816869e87 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -2,7 +2,6 @@ package graphql import graphql.analysis.MaxQueryComplexityInstrumentation import graphql.analysis.MaxQueryDepthInstrumentation -import graphql.collect.ImmutableKit import graphql.execution.AsyncExecutionStrategy import graphql.execution.AsyncSerialExecutionStrategy import graphql.execution.DataFetcherExceptionHandler @@ -26,7 +25,6 @@ import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry -import graphql.schema.GraphQLDirective import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLInterfaceType @@ -1417,28 +1415,6 @@ many lines'''] e.message.contains("an illegal value for the argument ") } - def "Applied schema directives arguments are validated for programmatic schemas"() { - given: - def arg = newArgument().name("arg").type(GraphQLInt).valueProgrammatic(ImmutableKit.emptyMap()).build() - def directive = GraphQLDirective.newDirective().name("cached").argument(arg).build() - def field = newFieldDefinition() - .name("hello") - .type(GraphQLString) - .argument(arg) - .withDirective(directive) - .build() - when: - newSchema().query( - newObject() - .name("Query") - .field(field) - .build()) - .build() - then: - def e = thrown(InvalidSchemaException) - e.message.contains("Invalid argument 'arg' for applied directive of name 'cached'") - } - def "getters work as expected"() { Instrumentation instrumentation = new SimpleInstrumentation() when: diff --git a/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy b/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy index 0e06c3b2b1..23a506621d 100644 --- a/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLArgumentTest.groovy @@ -1,12 +1,18 @@ package graphql.schema +import graphql.collect.ImmutableKit import graphql.language.FloatValue +import graphql.schema.validation.InvalidSchemaException import spock.lang.Specification import static graphql.Scalars.GraphQLFloat import static graphql.Scalars.GraphQLInt import static graphql.Scalars.GraphQLString +import static graphql.schema.GraphQLArgument.newArgument import static graphql.schema.GraphQLDirective.newDirective +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition +import static graphql.schema.GraphQLObjectType.newObject +import static graphql.schema.GraphQLSchema.newSchema class GraphQLArgumentTest extends Specification { @@ -189,4 +195,26 @@ class GraphQLArgumentTest extends Specification { resolvedDefaultValue == null } + def "Applied schema directives arguments are validated for programmatic schemas"() { + given: + def arg = newArgument().name("arg").type(GraphQLInt).valueProgrammatic(ImmutableKit.emptyMap()).build() // Retain for test coverage + def directive = GraphQLDirective.newDirective().name("cached").argument(arg).build() + def field = newFieldDefinition() + .name("hello") + .type(GraphQLString) + .argument(arg) + .withDirective(directive) + .build() + when: + newSchema().query( + newObject() + .name("Query") + .field(field) + .build()) + .build() + then: + def e = thrown(InvalidSchemaException) + e.message.contains("Invalid argument 'arg' for applied directive of name 'cached'") + } + } From f5f6b48b1f3f67c564d58f8f3b1c0aa6dd0dd539 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 16:46:09 +1000 Subject: [PATCH 130/294] Update directive tests --- .../TypeResolutionEnvironmentTest.groovy | 8 ++++---- .../QueryDirectivesIntegrationTest.groovy | 11 +++++----- .../graphql/schema/impl/SchemaUtilTest.groovy | 20 ++++++++++++++++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy b/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy index 9c773a3504..55be8cc947 100644 --- a/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy +++ b/src/test/groovy/graphql/TypeResolutionEnvironmentTest.groovy @@ -104,10 +104,10 @@ class TypeResolutionEnvironmentTest extends Specification { String source = env.getObject() assert source == "source" assert env.getGraphQLContext().get("a") == "b" - if ("FooBar" == env.getContext()) { + if ("FooBar" == env.getContext()) { // Retain for test coverage return schema.getObjectType("FooBar") } - if ("Foo" == env.getContext()) { + if ("Foo" == env.getContext()) { // Retain for test coverage return schema.getObjectType("FooImpl") } return null @@ -121,7 +121,7 @@ class TypeResolutionEnvironmentTest extends Specification { .field(mergedField(new Field("field"))) .fieldType(interfaceType) .schema(schema) - .context("FooBar") + .context("FooBar") // Retain for test coverage .graphQLContext(graphqlContext) .build() @@ -137,7 +137,7 @@ class TypeResolutionEnvironmentTest extends Specification { .field(mergedField(new Field("field"))) .fieldType(interfaceType) .schema(schema) - .context("Foo") + .context("Foo") // Retain for test coverage .graphQLContext(graphqlContext) .build() diff --git a/src/test/groovy/graphql/execution/directives/QueryDirectivesIntegrationTest.groovy b/src/test/groovy/graphql/execution/directives/QueryDirectivesIntegrationTest.groovy index c1e09c52eb..89a5d85a07 100644 --- a/src/test/groovy/graphql/execution/directives/QueryDirectivesIntegrationTest.groovy +++ b/src/test/groovy/graphql/execution/directives/QueryDirectivesIntegrationTest.groovy @@ -3,7 +3,6 @@ package graphql.execution.directives import graphql.GraphQL import graphql.TestUtil import graphql.schema.DataFetcher -import graphql.schema.GraphQLDirective import spock.lang.Specification /** @@ -74,7 +73,7 @@ class QueryDirectivesIntegrationTest extends Specification { graphql.execute({ input -> input.query(query).root(root) }) } - def joinArgs(List timeoutDirectives) { + static def joinArgs(List timeoutDirectives) { timeoutDirectives.collect({ def s = it.getName() + "(" it.arguments.forEach({ @@ -95,7 +94,7 @@ class QueryDirectivesIntegrationTest extends Specification { then: er.errors.isEmpty() - Map> immediateMap = capturedDirectives["review"].getImmediateDirectivesByName() + def immediateMap = capturedDirectives["review"].getImmediateAppliedDirectivesByName() def entries = immediateMap.entrySet().collectEntries({ [(it.getKey()): joinArgs(it.getValue())] }) @@ -103,7 +102,7 @@ class QueryDirectivesIntegrationTest extends Specification { timeout: "timeout(afterMillis:5),timeout(afterMillis:28),timeout(afterMillis:10)" ] - def immediate = capturedDirectives["review"].getImmediateDirective("cached") + def immediate = capturedDirectives["review"].getImmediateAppliedDirective("cached") joinArgs(immediate) == "cached(forMillis:5),cached(forMillis:10)" } @@ -123,7 +122,7 @@ class QueryDirectivesIntegrationTest extends Specification { then: er.errors.isEmpty() - Map> immediateMap = capturedDirectives["title"].getImmediateDirectivesByName() + def immediateMap = capturedDirectives["title"].getImmediateAppliedDirectivesByName() def entries = immediateMap.entrySet().collectEntries({ [(it.getKey()): joinArgs(it.getValue())] }) @@ -131,7 +130,7 @@ class QueryDirectivesIntegrationTest extends Specification { timeout: "timeout(afterMillis:99)" ] - def immediate = capturedDirectives["review"].getImmediateDirective("cached") + def immediate = capturedDirectives["review"].getImmediateAppliedDirective("cached") joinArgs(immediate) == "cached(forMillis:10)" } diff --git a/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy index 5b39a84aeb..19dc803bfa 100644 --- a/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy +++ b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy @@ -11,7 +11,6 @@ import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLType import graphql.schema.GraphQLTypeReference import graphql.schema.GraphQLUnionType -import graphql.schema.impl.SchemaUtil import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean @@ -163,8 +162,23 @@ class SchemaUtilTest extends Specification { when: GraphQLUnionType pet = ((GraphQLUnionType) SchemaWithReferences.getType("Pet")) GraphQLObjectType person = ((GraphQLObjectType) SchemaWithReferences.getType("Person")) - GraphQLArgument cacheEnabled = DirectivesUtil.directiveWithArg( - SchemaWithReferences.getDirectives(), Cache.getName(), "enabled").get(); + GraphQLArgument cacheEnabled = SchemaWithReferences.getDirectivesByName() + .get(Cache.getName()).getArgument("enabled") + + then: + SchemaWithReferences.allTypesAsList.findIndexOf { it instanceof GraphQLTypeReference } == -1 + pet.types.findIndexOf { it instanceof GraphQLTypeReference } == -1 + person.interfaces.findIndexOf { it instanceof GraphQLTypeReference } == -1 + !(cacheEnabled.getType() instanceof GraphQLTypeReference) + } + + def "all references are replaced with deprecated directiveWithArg"() { + when: + GraphQLUnionType pet = ((GraphQLUnionType) SchemaWithReferences.getType("Pet")) + GraphQLObjectType person = ((GraphQLObjectType) SchemaWithReferences.getType("Person")) + GraphQLArgument cacheEnabled = DirectivesUtil.directiveWithArg( // Retain for test coverage + SchemaWithReferences.getDirectives(), Cache.getName(), "enabled").get() + then: SchemaWithReferences.allTypesAsList.findIndexOf { it instanceof GraphQLTypeReference } == -1 pet.types.findIndexOf { it instanceof GraphQLTypeReference } == -1 From cf0abe987305bdb9e2d63b21cc2b991fd6a049c3 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 10 Sep 2022 17:08:25 +1000 Subject: [PATCH 131/294] Fix more applied directive deprecations, instrumentation deprecations --- .../schema/GraphQLDirectiveContainer.java | 6 ++-- .../MaxQueryDepthInstrumentationTest.groovy | 33 ++++++++++++++++--- .../execution/ExecutionStrategyTest.groovy | 5 +-- .../LegacyTestingInstrumentation.groovy | 30 ++++++++--------- ...dVisibilitySchemaTransformationTest.groovy | 21 +++++------- 5 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLDirectiveContainer.java b/src/main/java/graphql/schema/GraphQLDirectiveContainer.java index c18d9a67b3..52caebef29 100644 --- a/src/main/java/graphql/schema/GraphQLDirectiveContainer.java +++ b/src/main/java/graphql/schema/GraphQLDirectiveContainer.java @@ -9,15 +9,15 @@ import static graphql.collect.ImmutableKit.emptyList; /** - * Represents a graphql runtime type that can have {@link graphql.schema.GraphQLAppliedDirective}'s. + * Represents a graphql runtime type that can have {@link graphql.schema.GraphQLAppliedDirective}s. *

* Directives can be repeatable and (by default) non-repeatable. *

* There are access methods here that get the two different types. *

* The use of {@link GraphQLDirective} to represent a directive applied to an element is deprecated in favour of - * {@link GraphQLAppliedDirective}. A {@link GraphQLDirective} really should represent the definition of a directive in a schema not its use - * on schema elements. However, it has been left in place for legacy reasons and will be removed in a + * {@link GraphQLAppliedDirective}. A {@link GraphQLDirective} really should represent the definition of a directive in a schema, not its use + * on schema elements. However, it has been left in place for legacy reasons and will be removed in a * future version. * * @see graphql.language.DirectiveDefinition diff --git a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy index f7f66e9142..f33074f217 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy @@ -17,12 +17,11 @@ import java.util.function.Function class MaxQueryDepthInstrumentationTest extends Specification { - Document createQuery(String query) { + static Document createQuery(String query) { Parser parser = new Parser() parser.parseDocument(query) } - def "throws exception if too deep"() { given: def schema = TestUtil.schema(""" @@ -50,6 +49,32 @@ class MaxQueryDepthInstrumentationTest extends Specification { } def "doesn't throw exception if not deep enough"() { + given: + def schema = TestUtil.schema(""" + type Query{ + foo: Foo + bar: String + } + type Foo { + scalar: String + foo: Foo + } + """) + def query = createQuery(""" + {f1: foo {foo {foo {scalar}}} f2: foo { foo {foo {foo {foo{foo{scalar}}}}}} } + """) + MaxQueryDepthInstrumentation maximumQueryDepthInstrumentation = new MaxQueryDepthInstrumentation(7) + ExecutionInput executionInput = Mock(ExecutionInput) + def executionContext = executionCtx(executionInput, query, schema) + def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) + def state = maximumQueryDepthInstrumentation.createState(null) + when: + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters, state) + then: + notThrown(Exception) + } + + def "doesn't throw exception if not deep enough with deprecated beginExecuteOperation"() { given: def schema = TestUtil.schema(""" type Query{ @@ -69,7 +94,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { def executionContext = executionCtx(executionInput, query, schema) def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) // Retain for test coverage then: notThrown(Exception) } @@ -133,7 +158,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { !er.errors.isEmpty() } - private ExecutionContext executionCtx(ExecutionInput executionInput, Document query, GraphQLSchema schema) { + static private ExecutionContext executionCtx(ExecutionInput executionInput, Document query, GraphQLSchema schema) { ExecutionContextBuilder.newExecutionContextBuilder() .executionInput(executionInput).document(query).graphQLSchema(schema).executionId(ExecutionId.generate()).build() } diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index 0381ea9c07..b4d28e7f2c 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -10,6 +10,7 @@ import graphql.SerializationError import graphql.StarWarsSchema import graphql.TypeMismatchError import graphql.execution.instrumentation.InstrumentationContext +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters import graphql.language.Argument @@ -678,12 +679,12 @@ class ExecutionStrategyTest extends Specification { Map fetchedValues = [:] @Override - InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { + InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters, InstrumentationState state) { if (parameters.fetchedValue instanceof FetchedValue) { FetchedValue value = (FetchedValue) parameters.fetchedValue fetchedValues.put(parameters.field.name, value) } - return super.beginFieldComplete(parameters) + return super.beginFieldComplete(parameters, state) } } ExecutionContext instrumentedExecutionContext = executionContextBuilder.instrumentation(instrumentation).build() diff --git a/src/test/groovy/graphql/execution/instrumentation/LegacyTestingInstrumentation.groovy b/src/test/groovy/graphql/execution/instrumentation/LegacyTestingInstrumentation.groovy index 5e24cbf09f..e8a9478cb6 100644 --- a/src/test/groovy/graphql/execution/instrumentation/LegacyTestingInstrumentation.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/LegacyTestingInstrumentation.groovy @@ -39,85 +39,85 @@ class LegacyTestingInstrumentation implements Instrumentation { @Override InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage new TestingInstrumentContext("execution", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("parse", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("validation", executionList, throwableList, useOnDispatch) } @Override ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingExecutionStrategyInstrumentationContext("execution-strategy", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("execute-operation", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("subscribed-field-event-$parameters.field.name", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext beginField(InstrumentationFieldParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("field-$parameters.field.name", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("fetch-$parameters.field.name", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext> beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("complete-$parameters.field.name", executionList, throwableList, useOnDispatch) } @Override InstrumentationContext> beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return new TestingInstrumentContext("complete-list-$parameters.field.name", executionList, throwableList, useOnDispatch) } @Override GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return schema } @Override ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return executionInput } @Override ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return executionContext } @Override DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage dfClasses.add(dataFetcher.getClass()) return new DataFetcher() { @Override @@ -130,7 +130,7 @@ class LegacyTestingInstrumentation implements Instrumentation { @Override CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { - assert parameters.getInstrumentationState() == instrumentationState + assert parameters.getInstrumentationState() == instrumentationState // Retain for test coverage return CompletableFuture.completedFuture(executionResult) } } diff --git a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy index 4995ed029a..00b7edd505 100644 --- a/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy +++ b/src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy @@ -2,6 +2,7 @@ package graphql.schema.transform import graphql.Scalars import graphql.TestUtil +import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLInputObjectType @@ -11,7 +12,6 @@ import graphql.schema.TypeResolver import graphql.schema.idl.SchemaPrinter import spock.lang.Specification -import static graphql.schema.GraphQLDirective.newDirective import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition import static graphql.schema.GraphQLInterfaceType.newInterface import static graphql.schema.GraphQLObjectType.newObject @@ -20,7 +20,7 @@ import static graphql.schema.GraphQLTypeReference.typeRef class FieldVisibilitySchemaTransformationTest extends Specification { def visibilitySchemaTransformation = new FieldVisibilitySchemaTransformation({ environment -> - def directives = (environment.schemaElement as GraphQLDirectiveContainer).directives + def directives = (environment.schemaElement as GraphQLDirectiveContainer).appliedDirectives return directives.find({ directive -> directive.name == "private" }) == null }) @@ -941,11 +941,11 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .field(newFieldDefinition().name("account").type(typeRef("Account")).build()) .build() - def privateDirective = newDirective().name("private").build() + def privateDirective = GraphQLAppliedDirective.newDirective().name("private").build() def account = newObject() .name("Account") .field(newFieldDefinition().name("name").type(Scalars.GraphQLString).build()) - .field(newFieldDefinition().name("billingStatus").type(typeRef("SuperSecretCustomerData")).withDirective(privateDirective).build()) + .field(newFieldDefinition().name("billingStatus").type(typeRef("SuperSecretCustomerData")).withAppliedDirective(privateDirective).build()) .build() def billingStatus = newObject() @@ -970,7 +970,6 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .additionalType(account) .additionalType(billingStatus) .additionalType(secretData) - .additionalDirective(privateDirective) .build() when: @@ -991,11 +990,11 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .field(newFieldDefinition().name("account").type(typeRef("Account")).build()) .build() - def privateDirective = newDirective().name("private").build() + def privateDirective = GraphQLAppliedDirective.newDirective().name("private").build() def account = newObject() .name("Account") .field(newFieldDefinition().name("name").type(Scalars.GraphQLString).build()) - .field(newFieldDefinition().name("billingStatus").type(typeRef("BillingStatus")).withDirective(privateDirective).build()) + .field(newFieldDefinition().name("billingStatus").type(typeRef("BillingStatus")).withAppliedDirective(privateDirective).build()) .build() def billingStatus = newObject() @@ -1020,7 +1019,6 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .additionalType(account) .additionalType(billingStatus) .additionalType(secretData) - .additionalDirective(privateDirective) .build() when: @@ -1040,11 +1038,11 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .field(newFieldDefinition().name("account").type(typeRef("Account")).build()) .build() - def privateDirective = newDirective().name("private").build() + def privateDirective = GraphQLAppliedDirective.newDirective().name("private").build() def account = newObject() .name("Account") .field(newFieldDefinition().name("name").type(Scalars.GraphQLString).build()) - .field(newFieldDefinition().name("billingStatus").type(typeRef("BillingStatus")).withDirective(privateDirective).build()) + .field(newFieldDefinition().name("billingStatus").type(typeRef("BillingStatus")).withAppliedDirective(privateDirective).build()) .build() def billingStatus = newObject() @@ -1056,7 +1054,6 @@ class FieldVisibilitySchemaTransformationTest extends Specification { .query(query) .additionalType(billingStatus) .additionalType(account) - .additionalDirective(privateDirective) .build() when: @@ -1074,7 +1071,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification { def callbacks = [] def visibilitySchemaTransformation = new FieldVisibilitySchemaTransformation({ environment -> - def directives = (environment.schemaElement as GraphQLDirectiveContainer).directives + def directives = (environment.schemaElement as GraphQLDirectiveContainer).appliedDirectives return directives.find({ directive -> directive.name == "private" }) == null }, { -> callbacks << "before" }, { -> callbacks << "after"} ) From d0d06b7f91b2684f3054b2f4a97ee11fbda28bda Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 11 Sep 2022 09:29:41 +1000 Subject: [PATCH 132/294] Added new builder to ExecutionResult (#2939) * Added new builder to ExecutionResult.java * Removed usages of the impl --- src/main/java/graphql/ExecutionResult.java | 97 ++++++++++++++++++- .../java/graphql/ExecutionResultImpl.java | 18 ++-- .../graphql/cachecontrol/CacheControl.java | 2 +- .../groovy/graphql/ExecutionResultTest.groovy | 31 ++++++ .../cachecontrol/CacheControlTest.groovy | 6 +- .../IntrospectionResultToSchemaTest.groovy | 4 +- 6 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 src/test/groovy/graphql/ExecutionResultTest.groovy diff --git a/src/main/java/graphql/ExecutionResult.java b/src/main/java/graphql/ExecutionResult.java index e3715ee550..c05251ecc6 100644 --- a/src/main/java/graphql/ExecutionResult.java +++ b/src/main/java/graphql/ExecutionResult.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * This simple value class represents the result of performing a graphql query. @@ -34,7 +35,7 @@ public interface ExecutionResult { * See : https://graphql.github.io/graphql-spec/June2018/#sec-Data * * @return true if the entry "data" should be present in the result - * false otherwise + * false otherwise */ boolean isDataPresent(); @@ -54,4 +55,98 @@ public interface ExecutionResult { * @return a map of the result that strictly follows the spec */ Map toSpecification(); + + + /** + * This helps you transform the current {@link ExecutionResult} object into another one by starting a builder with all + * the current values and allows you to transform it how you want. + * + * @param builderConsumer the consumer code that will be given a builder to transform + * + * @return a new {@link ExecutionResult} object based on calling build on that builder + */ + default ExecutionResult transform(Consumer> builderConsumer) { + Builder builder = newExecutionResult().from(this); + builderConsumer.accept(builder); + return builder.build(); + } + + /** + * @return a builder that allows you to build a new execution result + */ + static Builder newExecutionResult() { + return ExecutionResultImpl.newExecutionResult(); + } + + interface Builder> { + + /** + * Sets values into the builder based on a previous {@link ExecutionResult} + * + * @param executionResult the previous {@link ExecutionResult} + * + * @return the builder + */ + B from(ExecutionResult executionResult); + + /** + * Sets new data into the builder + * + * @param data the data to use + * + * @return the builder + */ + B data(Object data); + + /** + * Sets error list as the errors for this builder + * + * @param errors the errors to use + * + * @return the builder + */ + B errors(List errors); + + /** + * Adds the error list to any existing the errors for this builder + * + * @param errors the errors to add + * + * @return the builder + */ + B addErrors(List errors); + + /** + * Adds the error to any existing the errors for this builder + * + * @param error the error to add + * + * @return the builder + */ + B addError(GraphQLError error); + + /** + * Sets the extension map for this builder + * + * @param extensions the extensions to use + * + * @return the builder + */ + B extensions(Map extensions); + + /** + * Adds a new entry into the extensions map for this builder + * + * @param key the key of the extension entry + * @param value the value of the extension entry + * + * @return the builder + */ + B addExtension(String key, Object value); + + /** + * @return a newly built {@link ExecutionResult} + */ + ExecutionResult build(); + } } diff --git a/src/main/java/graphql/ExecutionResultImpl.java b/src/main/java/graphql/ExecutionResultImpl.java index 1bb675bf07..bec1b5ac4c 100644 --- a/src/main/java/graphql/ExecutionResultImpl.java +++ b/src/main/java/graphql/ExecutionResultImpl.java @@ -104,22 +104,17 @@ public String toString() { '}'; } - public ExecutionResultImpl transform(Consumer builderConsumer) { - Builder builder = newExecutionResult().from(this); - builderConsumer.accept(builder); - return builder.build(); - } - public static Builder newExecutionResult() { return new Builder(); } - public static class Builder { + public static class Builder implements ExecutionResult.Builder { private boolean dataPresent; private Object data; private List errors = new ArrayList<>(); private Map extensions; + @Override public Builder from(ExecutionResult executionResult) { dataPresent = executionResult.isDataPresent(); data = executionResult.getData(); @@ -128,39 +123,46 @@ public Builder from(ExecutionResult executionResult) { return this; } + @Override public Builder data(Object data) { dataPresent = true; this.data = data; return this; } + @Override public Builder errors(List errors) { this.errors = errors; return this; } + @Override public Builder addErrors(List errors) { this.errors.addAll(errors); return this; } + @Override public Builder addError(GraphQLError error) { this.errors.add(error); return this; } + @Override public Builder extensions(Map extensions) { this.extensions = extensions; return this; } + @Override public Builder addExtension(String key, Object value) { this.extensions = (this.extensions == null ? new LinkedHashMap<>() : this.extensions); this.extensions.put(key, value); return this; } - public ExecutionResultImpl build() { + @Override + public ExecutionResult build() { return new ExecutionResultImpl(dataPresent, data, errors, extensions); } } diff --git a/src/main/java/graphql/cachecontrol/CacheControl.java b/src/main/java/graphql/cachecontrol/CacheControl.java index 2941c8a46f..7dc662ba5a 100644 --- a/src/main/java/graphql/cachecontrol/CacheControl.java +++ b/src/main/java/graphql/cachecontrol/CacheControl.java @@ -206,7 +206,7 @@ public static CacheControl newCacheControl() { @Deprecated @DeprecatedAt("2022-07-26") public ExecutionResult addTo(ExecutionResult executionResult) { - return ExecutionResultImpl.newExecutionResult() + return ExecutionResult.newExecutionResult() .from(executionResult) .addExtension(CACHE_CONTROL_EXTENSION_KEY, hintsToCacheControlProperties()) .build(); diff --git a/src/test/groovy/graphql/ExecutionResultTest.groovy b/src/test/groovy/graphql/ExecutionResultTest.groovy new file mode 100644 index 0000000000..67e411941c --- /dev/null +++ b/src/test/groovy/graphql/ExecutionResultTest.groovy @@ -0,0 +1,31 @@ +package graphql + +import graphql.language.SourceLocation +import spock.lang.Specification + +/** + * Most of the tests are actually in ExecutionResultImplTest since this is the actual impl + */ +class ExecutionResultTest extends Specification { + + def error1 = new InvalidSyntaxError(new SourceLocation(966, 964), "Yowza") + + def "can use builder to build it"() { + when: + ExecutionResult er = ExecutionResult.newExecutionResult().data([a: "b"]).addError(error1).addExtension("x", "y").build() + then: + er.data == [a: "b"] + er.errors == [error1] + er.extensions == [x: "y"] + } + + def "can transform"() { + when: + ExecutionResult er = ExecutionResult.newExecutionResult().data([a: "b"]).addError(error1).addExtension("x", "y").build() + er = er.transform({ bld -> bld.addExtension("foo", "bar") }) + then: + er.data == [a: "b"] + er.errors == [error1] + er.extensions == [x: "y", foo: "bar"] + } +} diff --git a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy index 6365289004..c05482e3c9 100644 --- a/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy +++ b/src/test/groovy/graphql/cachecontrol/CacheControlTest.groovy @@ -1,7 +1,7 @@ package graphql.cachecontrol import graphql.ExecutionInput -import graphql.ExecutionResultImpl +import graphql.ExecutionResult import graphql.GraphQLContext import graphql.TestUtil import graphql.execution.CoercedVariables @@ -29,7 +29,7 @@ class CacheControlTest extends Specification { cc.hint(ResultPath.parse("/hint/33/private"), 33, CacheControl.Scope.PRIVATE) cc.hint(ResultPath.parse("/hint/private"), CacheControl.Scope.PRIVATE) - def er = ExecutionResultImpl.newExecutionResult().data("data").build() + def er = ExecutionResult.newExecutionResult().data("data").build() when: def newER = cc.addTo(er) @@ -56,7 +56,7 @@ class CacheControlTest extends Specification { def startingExtensions = ["someExistingExt": "data"] - def er = ExecutionResultImpl.newExecutionResult().data("data").extensions(startingExtensions).build() + def er = ExecutionResult.newExecutionResult().data("data").extensions(startingExtensions).build() when: def newER = cc.addTo(er) diff --git a/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy index f140b65a15..9f2346df4f 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionResultToSchemaTest.groovy @@ -3,7 +3,7 @@ package graphql.introspection import com.fasterxml.jackson.databind.ObjectMapper import graphql.Assert import graphql.ExecutionInput -import graphql.ExecutionResultImpl +import graphql.ExecutionResult import graphql.GraphQL import graphql.TestUtil import graphql.language.Document @@ -700,7 +700,7 @@ input InputType { def "create schema fail"() { given: - def failResult = ExecutionResultImpl.newExecutionResult().build() + def failResult = ExecutionResult.newExecutionResult().build() when: Document document = introspectionResultToSchema.createSchemaDefinition(failResult) From 4854ab5bf20dfafe8903e10b4931bd15ad53452b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 27 Sep 2022 16:50:49 +1000 Subject: [PATCH 133/294] Fix up Java deprecations in tests --- .../instrumentation/Instrumentation.java | 2 +- .../example/http/ExecutionResultJSONTesting.java | 2 +- src/test/groovy/example/http/HttpMain.java | 7 ++++--- src/test/groovy/graphql/NestedInputSchema.java | 2 +- src/test/groovy/graphql/ScalarsQuerySchema.java | 2 +- .../execution/AsyncExecutionStrategyTest.groovy | 3 ++- .../DataFetcherExceptionHandlerTest.groovy | 15 --------------- ...trategyExceptionHandlingEquivalenceTest.groovy | 5 +++-- .../groovy/graphql/execution/ExecutionTest.groovy | 3 ++- .../dataloader/BatchCompareDataFetchers.java | 5 +++-- .../DataLoaderCompanyProductBackend.java | 5 ++--- ...DataLoaderDispatcherInstrumentationTest.groovy | 3 ++- .../dataloader/DataLoaderHangingTest.groovy | 11 ++++++----- src/test/groovy/graphql/parser/ParserTest.groovy | 2 +- .../DelegatingDataFetchingEnvironmentTest.groovy | 2 +- 15 files changed, 30 insertions(+), 39 deletions(-) diff --git a/src/main/java/graphql/execution/instrumentation/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/Instrumentation.java index 11b41e48f5..4042e680a2 100644 --- a/src/main/java/graphql/execution/instrumentation/Instrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/Instrumentation.java @@ -468,7 +468,7 @@ default GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExec * * @return a non null instrumented ExecutionContext, the default is to return to the same object * - * @deprecated use {@link #instrumentExecutionContext(ExecutionContext, InstrumentationExecutionParameters)} instead + * @deprecated use {@link #instrumentExecutionContext(ExecutionContext, InstrumentationExecutionParameters, InstrumentationState)} instead */ @Deprecated @DeprecatedAt("2022-07-26") diff --git a/src/test/groovy/example/http/ExecutionResultJSONTesting.java b/src/test/groovy/example/http/ExecutionResultJSONTesting.java index 0ef1891d9a..74f5328a0d 100644 --- a/src/test/groovy/example/http/ExecutionResultJSONTesting.java +++ b/src/test/groovy/example/http/ExecutionResultJSONTesting.java @@ -70,7 +70,7 @@ private void testGson(HttpServletResponse response, Object er) throws IOExceptio private ExecutionResult createER() { List errors = new ArrayList<>(); - errors.add(new ValidationError(ValidationErrorType.UnknownType, mkLocations(), "Test ValidationError")); + errors.add(new ValidationError(ValidationErrorType.UnknownType, mkLocations(), "Test ValidationError")); // Retain as there is no alternative constructor for ValidationError errors.add(new MissingRootTypeException("Mutations are not supported.", null)); errors.add(new InvalidSyntaxError(mkLocations(), "Not good syntax m'kay")); errors.add(new NonNullableFieldWasNullError(new NonNullableFieldWasNullException(mkExecutionInfo(), mkPath()))); diff --git a/src/test/groovy/example/http/HttpMain.java b/src/test/groovy/example/http/HttpMain.java index 7fb2e4b08f..e705df61d6 100644 --- a/src/test/groovy/example/http/HttpMain.java +++ b/src/test/groovy/example/http/HttpMain.java @@ -18,6 +18,7 @@ import graphql.schema.idl.TypeDefinitionRegistry; import org.dataloader.BatchLoader; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; import org.dataloader.DataLoaderRegistry; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; @@ -46,7 +47,7 @@ import static java.util.Arrays.asList; /** - * An very simple example of serving a qraphql schema over http. + * A very simple example of serving a graphql schema over http. *

* More info can be found here : http://graphql.org/learn/serving-over-http/ */ @@ -178,7 +179,7 @@ private DataLoaderRegistry buildDataLoaderRegistry() { CompletableFuture.supplyAsync(() -> loadCharactersViaHTTP(keys)); - DataLoader friendsDataLoader = new DataLoader<>(friendsBatchLoader); + DataLoader friendsDataLoader = DataLoaderFactory.newDataLoader(friendsBatchLoader); DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry(); // @@ -275,7 +276,7 @@ private Reader loadSchemaFile(String name) { } // Lots of the data happens to be maps of objects and this allows us to get back into type safety land - // with less boiler plate and casts + // with less boilerplate and casts // @SuppressWarnings("TypeParameterUnusedInFormals") private T asMapGet(Object mapObj, Object mapKey) { diff --git a/src/test/groovy/graphql/NestedInputSchema.java b/src/test/groovy/graphql/NestedInputSchema.java index 12e690a433..6e19825914 100644 --- a/src/test/groovy/graphql/NestedInputSchema.java +++ b/src/test/groovy/graphql/NestedInputSchema.java @@ -63,7 +63,7 @@ public static GraphQLObjectType rootType() { .argument(GraphQLArgument.newArgument() .name("intialValue") .type(GraphQLInt) - .defaultValue(5)) + .defaultValueProgrammatic(5)) .argument(GraphQLArgument.newArgument() .name("filter") .type(filterType()))) diff --git a/src/test/groovy/graphql/ScalarsQuerySchema.java b/src/test/groovy/graphql/ScalarsQuerySchema.java index 6d2cd8ce9f..0967171422 100644 --- a/src/test/groovy/graphql/ScalarsQuerySchema.java +++ b/src/test/groovy/graphql/ScalarsQuerySchema.java @@ -22,7 +22,7 @@ public class ScalarsQuerySchema { .field(newFieldDefinition() .name("floatNaN") .type(Scalars.GraphQLFloat) - .staticValue(Double.NaN)) + .staticValue(Double.NaN)) // Retain for test coverage // Scalars with input of same type, value echoed back .field(newFieldDefinition() .name("floatNaNInput") diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 90f9afc2ba..8d3e76607e 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -4,6 +4,7 @@ import graphql.ErrorType import graphql.ExecutionResult import graphql.GraphQLContext import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.language.Field @@ -267,7 +268,7 @@ class AsyncExecutionStrategyTest extends Specification { .locale(Locale.getDefault()) .instrumentation(new SimpleInstrumentation() { @Override - ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { + ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState state) { return new ExecutionStrategyInstrumentationContext() { @Override diff --git a/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerTest.groovy b/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerTest.groovy index 8a711ab216..0587dc4e8a 100644 --- a/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerTest.groovy +++ b/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerTest.groovy @@ -76,11 +76,6 @@ class DataFetcherExceptionHandlerTest extends Specification { def "integration test to prove an async custom error handle can be made"() { DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() { - @Override - DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { - return null - } - @Override CompletableFuture handleException(DataFetcherExceptionHandlerParameters params) { def msg = "The thing went " + params.getException().getMessage() @@ -117,11 +112,6 @@ class DataFetcherExceptionHandlerTest extends Specification { def "if an async exception handler itself throws an exception than that is handled"() { DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() { - @Override - DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { - return null - } - @Override CompletableFuture handleException(DataFetcherExceptionHandlerParameters handlerParameters) { throw new RuntimeException("The handler itself went BANG!") @@ -140,11 +130,6 @@ class DataFetcherExceptionHandlerTest extends Specification { def "multiple errors can be returned in a handler"() { DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() { - @Override - DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { - return null - } - @Override CompletableFuture handleException(DataFetcherExceptionHandlerParameters params) { def result = DataFetcherExceptionHandlerResult.newResult() diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy index 7a47cee7ad..cbbbf34bcd 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy @@ -4,6 +4,7 @@ import graphql.ExecutionInput import graphql.GraphQL import graphql.StarWarsSchema import graphql.execution.instrumentation.InstrumentationContext +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import graphql.validation.ValidationError @@ -16,8 +17,8 @@ class ExecutionStrategyExceptionHandlingEquivalenceTest extends Specification { class TestInstrumentation extends SimpleInstrumentation { @Override - InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - throw new AbortExecutionException([new ValidationError(ValidationErrorType.UnknownType)]) + InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState state) { + throw new AbortExecutionException([new ValidationError(ValidationErrorType.UnknownType)]) // Retain as there is no alternative constructor for ValidationError } } diff --git a/src/test/groovy/graphql/execution/ExecutionTest.groovy b/src/test/groovy/graphql/execution/ExecutionTest.groovy index c2539ef6c6..d6d972a7f6 100644 --- a/src/test/groovy/graphql/execution/ExecutionTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionTest.groovy @@ -115,7 +115,8 @@ class ExecutionTest extends Specification { @Override ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, - InstrumentationExecutionParameters parameters) { + InstrumentationExecutionParameters parameters, + InstrumentationState state) { return ExecutionContextBuilder.newExecutionContextBuilder(executionContext) .queryStrategy(queryStrategyUpdatedToDuringExecutionContextInstrument) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java b/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java index 0569e667ee..430f79a74c 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java @@ -6,6 +6,7 @@ import graphql.schema.DataFetcher; import org.dataloader.BatchLoader; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; import java.util.ArrayList; import java.util.Arrays; @@ -99,7 +100,7 @@ private static List> getDepartmentsForShops(List shops) { return completedFuture(getDepartmentsForShops(shopList)); }); - public DataLoader> departmentsForShopDataLoader = new DataLoader<>(departmentsForShopsBatchLoader); + public DataLoader> departmentsForShopDataLoader = DataLoaderFactory.newDataLoader(departmentsForShopsBatchLoader); public DataFetcher>> departmentsForShopDataLoaderDataFetcher = environment -> { Shop shop = environment.getSource(); @@ -136,7 +137,7 @@ private static List> getProductsForDepartments(List de return completedFuture(getProductsForDepartments(d)); }); - public DataLoader> productsForDepartmentDataLoader = new DataLoader<>(productsForDepartmentsBatchLoader); + public DataLoader> productsForDepartmentDataLoader = DataLoaderFactory.newDataLoader(productsForDepartmentsBatchLoader); public DataFetcher>> productsForDepartmentDataLoaderDataFetcher = environment -> { Department department = environment.getSource(); diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java index c49f1abbe4..51d2353bf7 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java @@ -3,9 +3,8 @@ import com.google.common.collect.ImmutableList; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -27,7 +26,7 @@ public DataLoaderCompanyProductBackend(int companyCount, int projectCount) { mkCompany(projectCount); } - projectsLoader = new DataLoader<>(keys -> getProjectsForCompanies(keys).thenApply(projects -> keys + projectsLoader = DataLoaderFactory.newDataLoader(keys -> getProjectsForCompanies(keys).thenApply(projects -> keys .stream() .map(companyId -> projects.stream() .filter(project -> project.getCompanyId().equals(companyId)) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy index fd8a29980e..ad553a76c2 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy @@ -10,6 +10,7 @@ import graphql.execution.ExecutionContext import graphql.execution.ExecutionStrategyParameters import graphql.execution.instrumentation.ChainedInstrumentation import graphql.execution.instrumentation.Instrumentation +import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.schema.DataFetcher @@ -132,7 +133,7 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { def dlInstrumentation = new DataLoaderDispatcherInstrumentation() def enhancingInstrumentation = new SimpleInstrumentation() { @Override - ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { + ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters, InstrumentationState state) { assert executionInput.getDataLoaderRegistry() == startingDataLoaderRegistry return executionInput.transform({ builder -> builder.dataLoaderRegistry(enhancedDataLoaderRegistry) }) } diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy index e8352969c1..1a5c68c674 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy @@ -19,6 +19,7 @@ import graphql.schema.idl.RuntimeWiring import org.apache.commons.lang3.concurrent.BasicThreadFactory import org.dataloader.BatchLoader import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderOptions import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -178,7 +179,7 @@ class DataLoaderHangingTest extends Specification { } private DataLoaderRegistry mkNewDataLoaderRegistry(executor) { - def dataLoaderAlbums = new DataLoader(new BatchLoader>() { + def dataLoaderAlbums = DataLoaderFactory.newDataLoader(new BatchLoader>() { @Override CompletionStage>> load(List keys) { return CompletableFuture.supplyAsync({ @@ -195,7 +196,7 @@ class DataLoaderHangingTest extends Specification { } }, DataLoaderOptions.newOptions().setMaxBatchSize(5)) - def dataLoaderSongs = new DataLoader(new BatchLoader>() { + def dataLoaderSongs = DataLoaderFactory.newDataLoader(new BatchLoader>() { @Override CompletionStage>> load(List keys) { return CompletableFuture.supplyAsync({ @@ -242,7 +243,7 @@ class DataLoaderHangingTest extends Specification { DataFetcherExceptionHandler customExceptionHandlerThatThrows = new DataFetcherExceptionHandler() { @Override - DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { + DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) { // Retain for test coverage, intentionally using sync version. throw handlerParameters.exception } } @@ -332,8 +333,8 @@ class DataLoaderHangingTest extends Specification { """ private DataLoaderRegistry buildRegistry() { - DataLoader personDataLoader = new DataLoader<>(personBatchLoader) - DataLoader companyDataLoader = new DataLoader<>(companyBatchLoader) + DataLoader personDataLoader = DataLoaderFactory.newDataLoader(personBatchLoader) + DataLoader companyDataLoader = DataLoaderFactory.newDataLoader(companyBatchLoader) DataLoaderRegistry registry = new DataLoaderRegistry() registry.register("person", personDataLoader) diff --git a/src/test/groovy/graphql/parser/ParserTest.groovy b/src/test/groovy/graphql/parser/ParserTest.groovy index 539e7b0c2d..8f461ec182 100644 --- a/src/test/groovy/graphql/parser/ParserTest.groovy +++ b/src/test/groovy/graphql/parser/ParserTest.groovy @@ -886,7 +886,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" when: Parser parser = new Parser() { @Override - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { // this pattern is used in Nadel - its backdoor but needed return new GraphqlAntlrToLanguage(tokens, multiSourceReader) { @Override diff --git a/src/test/groovy/graphql/schema/DelegatingDataFetchingEnvironmentTest.groovy b/src/test/groovy/graphql/schema/DelegatingDataFetchingEnvironmentTest.groovy index 3384a8ab28..687a0f62ae 100644 --- a/src/test/groovy/graphql/schema/DelegatingDataFetchingEnvironmentTest.groovy +++ b/src/test/groovy/graphql/schema/DelegatingDataFetchingEnvironmentTest.groovy @@ -28,7 +28,7 @@ class DelegatingDataFetchingEnvironmentTest extends Specification { } @Override - def getContext() { + def getContext() { // Retain for test coverage return "overriddenContext" } } From 575946bfbc403ad2e90ad1a8f783a676ee2a45c8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:25:37 +1000 Subject: [PATCH 134/294] Fix typeResolver in StarWars and TypeReference schemas and fix tests --- src/test/groovy/graphql/StarWarsSchema.java | 2 +- .../groovy/graphql/TypeReferenceSchema.java | 10 ++++++--- .../schema/GraphQLInputObjectTypeTest.groovy | 5 ++++- .../GraphqlFieldVisibilityTest.groovy | 21 ++++++++++++------- .../validation/ValidationUtilTest.groovy | 5 ++++- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/test/groovy/graphql/StarWarsSchema.java b/src/test/groovy/graphql/StarWarsSchema.java index 8563df2e96..bf5b891c02 100644 --- a/src/test/groovy/graphql/StarWarsSchema.java +++ b/src/test/groovy/graphql/StarWarsSchema.java @@ -55,7 +55,6 @@ public class StarWarsSchema { .name("appearsIn") .description("Which movies they appear in.") .type(list(episodeEnum))) - .typeResolver(StarWarsData.getCharacterTypeResolver()) .comparatorRegistry(BY_NAME_REGISTRY) .build(); @@ -180,6 +179,7 @@ public class StarWarsSchema { .dataFetcher(humanCoordinates, humanDataFetcher) .dataFetcher(droidCoordinates, droidDataFetcher) .dataFetcher(createHumanCoordinates, createHumanDataFetcher) + .typeResolver("Character", StarWarsData.getCharacterTypeResolver()) .build(); public static GraphQLSchema starWarsSchema = GraphQLSchema.newSchema() diff --git a/src/test/groovy/graphql/TypeReferenceSchema.java b/src/test/groovy/graphql/TypeReferenceSchema.java index 22788c3699..1fc323324f 100644 --- a/src/test/groovy/graphql/TypeReferenceSchema.java +++ b/src/test/groovy/graphql/TypeReferenceSchema.java @@ -2,6 +2,7 @@ import graphql.schema.Coercing; import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; @@ -148,7 +149,6 @@ public Boolean parseLiteral(Object input) { .name("Pet") .possibleType(GraphQLTypeReference.typeRef(CatType.getName())) .possibleType(GraphQLTypeReference.typeRef(DogType.getName())) - .typeResolver(new TypeResolverProxy()) .withDirective(unionDirective) .build(); } @@ -171,7 +171,6 @@ public Boolean parseLiteral(Object input) { Addressable = GraphQLInterfaceType.newInterface() .name("Addressable") - .typeResolver(new TypeResolverProxy()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("address") .type(GraphQLString)) @@ -316,6 +315,11 @@ public Boolean parseLiteral(Object input) { .type(QueryDirectiveInput)) .build(); + public static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver("Pet", new TypeResolverProxy()) + .typeResolver("Addressable", new TypeResolverProxy()) + .build(); + public static GraphQLSchema SchemaWithReferences = GraphQLSchema.newSchema() .query(PersonService) .additionalTypes(new HashSet<>(Arrays.asList(PersonType, PersonInputType, PetType, CatType, DogType, NamedType, HairStyle, OnOff))) @@ -330,6 +334,6 @@ public Boolean parseLiteral(Object input) { .additionalDirective(enumDirective) .additionalDirective(enumValueDirective) .additionalDirective(interfaceDirective) - + .codeRegistry(codeRegistry) .build(); } diff --git a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy index 7aa58093ec..81a7d19c20 100644 --- a/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLInputObjectTypeTest.groovy @@ -62,7 +62,10 @@ class GraphQLInputObjectTypeTest extends Specification { def "deprecated default value builder works"() { given: def graphQLContext = GraphQLContext.getDefault() - def schema = GraphQLSchema.newSchema().query(StarWarsSchema.queryType).build() + def schema = GraphQLSchema.newSchema() + .query(StarWarsSchema.queryType) + .codeRegistry(StarWarsSchema.codeRegistry) + .build() def validationUtil = new ValidationUtil() def inputObjectType = GraphQLInputObjectType.newInputObject() .name("inputObjectType") diff --git a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy index d80f7e3b6d..9f3e083977 100644 --- a/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy +++ b/src/test/groovy/graphql/schema/visibility/GraphqlFieldVisibilityTest.groovy @@ -30,6 +30,7 @@ class GraphqlFieldVisibilityTest extends Specification { GraphqlFieldVisibility banNameVisibility = newBlock().addPattern(".*\\.name").build() def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) + .codeRegistry(StarWarsSchema.codeRegistry) .fieldVisibility(banNameVisibility) // Retain deprecated builder for test coverage .build() @@ -58,9 +59,9 @@ class GraphqlFieldVisibilityTest extends Specification { def "introspection visibility is enforced"() { given: - GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .fieldVisibility(fieldVisibility) - .build() + GraphQLCodeRegistry codeRegistry = StarWarsSchema.codeRegistry.transform(builder -> { + builder.fieldVisibility(fieldVisibility) + }) def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) .codeRegistry(codeRegistry) @@ -94,9 +95,9 @@ class GraphqlFieldVisibilityTest extends Specification { def "introspection turned off via field visibility"() { given: - GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .fieldVisibility(NO_INTROSPECTION_FIELD_VISIBILITY) - .build() + GraphQLCodeRegistry codeRegistry = StarWarsSchema.codeRegistry.transform(builder -> { + builder.fieldVisibility(NO_INTROSPECTION_FIELD_VISIBILITY) + }) def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) .codeRegistry(codeRegistry) @@ -118,7 +119,9 @@ class GraphqlFieldVisibilityTest extends Specification { def "schema printing filters on visibility"() { when: - def codeRegistry = GraphQLCodeRegistry.newCodeRegistry().fieldVisibility(DEFAULT_FIELD_VISIBILITY).build() + def codeRegistry = StarWarsSchema.codeRegistry.transform(builder -> { + builder.fieldVisibility(DEFAULT_FIELD_VISIBILITY) + }) def schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) .codeRegistry(codeRegistry) @@ -200,7 +203,9 @@ enum Episode { // and with specific bans when: - codeRegistry = GraphQLCodeRegistry.newCodeRegistry().fieldVisibility(ban(['Droid.id', 'Character.name', "QueryType.hero"])).build() + codeRegistry = StarWarsSchema.codeRegistry.transform(builder -> { + builder.fieldVisibility(ban(['Droid.id', 'Character.name', "QueryType.hero"])) + }) schema = GraphQLSchema.newSchema() .query(StarWarsSchema.queryType) .codeRegistry(codeRegistry) diff --git a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy index 761ea166ce..3373809109 100644 --- a/src/test/groovy/graphql/validation/ValidationUtilTest.groovy +++ b/src/test/groovy/graphql/validation/ValidationUtilTest.groovy @@ -26,7 +26,10 @@ import static graphql.schema.GraphQLNonNull.nonNull class ValidationUtilTest extends Specification { - def schema = GraphQLSchema.newSchema().query(StarWarsSchema.queryType).build() + def schema = GraphQLSchema.newSchema() + .query(StarWarsSchema.queryType) + .codeRegistry(StarWarsSchema.codeRegistry) + .build() def validationUtil = new ValidationUtil() def graphQLContext = GraphQLContext.getDefault() def locale = Locale.getDefault() From 6278fa9c129b70c83539ab0c629579ad82df35f4 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 30 Sep 2022 10:31:39 +1000 Subject: [PATCH 135/294] cleanup --- .../diffing/FillupIsolatedVertices.java | 144 ------------------ .../graphql/schema/diffing/SchemaDiffing.java | 39 ++--- .../schema/diffing/SchemaDiffingTest.groovy | 22 +++ 3 files changed, 32 insertions(+), 173 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index af92da555c..748c0e33dd 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -667,152 +667,8 @@ public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { return possibleMappings.containsEntry(sourceVertex, targetVertex); } -// public boolean mappingPossibleForIsolatedSource(Vertex isolatedSourceVertex, Vertex targetVertex) { -// List contexts = typeContexts.get(targetVertex.getType()); -// Assert.assertNotNull(contexts); -// List contextForVertex = new ArrayList<>(); -// for (IsolatedVertexContext isolatedVertexContext : contexts) { -// contextForVertex.add(isolatedVertexContext.idForVertex(targetVertex, targetGraph)); -// } -// if (!targetVertex.getType().equals(DUMMY_TYPE_VERTEX)) { -// contextForVertex.add(targetVertex.getName()); -// } -// while (contextForVertex.size() > 0) { -// if (isolatedVertices.contextToIsolatedSourceVertices.containsKey(contextForVertex)) { -// return isolatedVertices.contextToIsolatedSourceVertices.get(contextForVertex).contains(isolatedSourceVertex); -// } -// contextForVertex.remove(contextForVertex.size() - 1); -// } -// return false; -// } -// -// public boolean mappingPossibleForIsolatedTarget(Vertex sourceVertex, Vertex isolatedTargetVertex) { -// List contexts = typeContexts.get(sourceVertex.getType()); -// Assert.assertNotNull(contexts); -// List contextForVertex = new ArrayList<>(); -// for (IsolatedVertexContext isolatedVertexContext : contexts) { -// contextForVertex.add(isolatedVertexContext.idForVertex(sourceVertex, sourceGraph)); -// } -// if (!sourceVertex.getType().equals(DUMMY_TYPE_VERTEX)) { -// contextForVertex.add(sourceVertex.getName()); -// } -// while (contextForVertex.size() > 0) { -// if (isolatedVertices.contextToIsolatedTargetVertices.containsKey(contextForVertex)) { -// return isolatedVertices.contextToIsolatedTargetVertices.get(contextForVertex).contains(isolatedTargetVertex); -// } -// contextForVertex.remove(contextForVertex.size() - 1); -// } -// return false; -// -// } - } - - - private void calcIsolatedVertices(List contexts, String typeNameForDebug) { - Collection currentSourceVertices = sourceGraph.getVertices(); - Collection currentTargetVertices = targetGraph.getVertices(); - calcIsolatedVerticesImpl(currentSourceVertices, currentTargetVertices, Collections.emptyList(), 0, contexts, new LinkedHashSet<>(), new LinkedHashSet<>(), typeNameForDebug); } - /** - * - */ - private void calcIsolatedVerticesImpl( - Collection currentSourceVertices, - Collection currentTargetVertices, - List currentContextId, - int curContextSegmentIx, - List contextSegments, - Set usedSourceVertices, - Set usedTargetVertices, - String typeNameForDebug) { - - /** - * the elements grouped by the current context segment. - */ - - VertexContextSegment finalCurrentContext = contextSegments.get(curContextSegmentIx); - Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices, - v -> finalCurrentContext.filter(v, sourceGraph), - v -> finalCurrentContext.idForVertex(v, sourceGraph)); - Map> targetGroups = FpKit.filterAndGroupingBy(currentTargetVertices, - v -> finalCurrentContext.filter(v, targetGraph), - v -> finalCurrentContext.idForVertex(v, targetGraph)); - - // all of the relevant vertices will be handled - - - if (curContextSegmentIx == 0) { - if (sourceGroups.size() == 0 && targetGroups.size() == 1) { - // we only have inserted elements - String context = targetGroups.keySet().iterator().next(); - int count = targetGroups.get(context).size(); - Set newSourceVertices = Vertex.newIsolatedNodes(count, "source-isolated-" + typeNameForDebug + "-"); - isolatedVertices.putSource(Arrays.asList(context), newSourceVertices); - } else if (sourceGroups.size() == 1 && targetGroups.size() == 0) { - // we only have deleted elements - String context = sourceGroups.keySet().iterator().next(); - int count = sourceGroups.get(context).size(); - Set newTargetVertices = Vertex.newIsolatedNodes(count, "target-isolated-" + typeNameForDebug + "-"); - isolatedVertices.putTarget(Arrays.asList(context), newTargetVertices); - } - } - List deletedContexts = new ArrayList<>(); - List insertedContexts = new ArrayList<>(); - List sameContexts = new ArrayList<>(); - diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); - for (String sameContext : sameContexts) { - ImmutableList sourceVertices = sourceGroups.get(sameContext); - ImmutableList targetVertices = targetGroups.get(sameContext); - List newContextId = concat(currentContextId, sameContext); - if (contextSegments.size() > curContextSegmentIx + 1) { - calcIsolatedVerticesImpl(sourceVertices, targetVertices, newContextId, curContextSegmentIx + 1, contextSegments, usedSourceVertices, usedTargetVertices, typeNameForDebug); - } - - Set notUsedSource = new LinkedHashSet<>(sourceVertices); - notUsedSource.removeAll(usedSourceVertices); - Set notUsedTarget = new LinkedHashSet<>(targetVertices); - notUsedTarget.removeAll(usedTargetVertices); - - /** - * We know that the first context is just by type and we have all the remaining vertices of the same - * type here. - */ - if (curContextSegmentIx == 0) { - if (notUsedSource.size() > notUsedTarget.size()) { - Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); - // all deleted vertices can map to all new TargetVertices - for (Vertex deletedVertex : notUsedSource) { - isolatedVertices.putTarget(Arrays.asList(sameContext), newTargetVertices); - } - } else if (notUsedTarget.size() > notUsedSource.size()) { - Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); - // all inserted fields can map to all new source vertices - for (Vertex insertedVertex : notUsedTarget) { - isolatedVertices.putSource(Arrays.asList(sameContext), newSourceVertices); - } - } - } else { - usedSourceVertices.addAll(sourceGroups.get(sameContext)); - usedTargetVertices.addAll(targetGroups.get(sameContext)); - if (notUsedSource.size() > notUsedTarget.size()) { - Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); - // all deleted vertices can map to all new TargetVertices - for (Vertex deletedVertex : notUsedSource) { - isolatedVertices.putTarget(concat(newContextId, deletedVertex.getName()), newTargetVertices); - } - } else if (notUsedTarget.size() > notUsedSource.size()) { - Set newSourceVertices = Vertex.newIsolatedNodes(notUsedTarget.size() - notUsedSource.size(), "source-isolated-" + typeNameForDebug + "-"); - // all inserted fields can map to all new source vertices - for (Vertex insertedVertex : notUsedTarget) { - isolatedVertices.putSource(concat(newContextId, insertedVertex.getName()), newSourceVertices); - } - } - } - - } - - } public void calcPossibleMappings(List contexts, String typeNameForDebug) { Collection currentSourceVertices = sourceGraph.getVertices(); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index c461239a16..1897f15690 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -159,6 +159,7 @@ public static void diffNamedList(Set sourceNames, List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); + PossibleMappings possibleMappings = new PossibleMappings(sourceGraph, targetGraph); FillupIsolatedVertices fillupIsolatedVertices = new FillupIsolatedVertices(sourceGraph, targetGraph); fillupIsolatedVertices.ensureGraphAreSameSize(); FillupIsolatedVertices.IsolatedVertices isolatedVertices = fillupIsolatedVertices.isolatedVertices; @@ -408,44 +409,22 @@ private void generateChildren(MappingEntry parentEntry, Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); - List> callables = new ArrayList<>(); // costMatrix[0] is the row for v_i for (int i = level - 1; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); - int finalI = i; - Callable callable = () -> { - try { - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); - costMatrix[finalI - level + 1].set(j, cost); - costMatrixCopy[finalI - level + 1].set(j, cost); -// if (v.getType().equals("AppliedArgument") && ((String) v.get("value")).contains("jira-software") && cost != Integer.MAX_VALUE) { -// System.out.println((finalI - level + 1) + ", " + j + ": cost: " + cost + " for " + v + " to " + u); -// } - j++; - } - return null; - } catch (Throwable t) { - t.printStackTrace(); - return null; - } - }; - callables.add(callable); -// callable.call(); + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); + costMatrix[i - level + 1].set(j, cost); + costMatrixCopy[i - level + 1].set(j, cost); + j++; + } } - forkJoinPool.invokeAll(callables); - forkJoinPool.awaitTermination(10000, TimeUnit.DAYS); - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); int[] assignments = hungarianAlgorithm.execute(); int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); -// if (costMatrixSum >= Integer.MAX_VALUE) { -// logUnmappable(costMatrixCopy, assignments, sourceList, availableTargetVertices, level); -// throw new RuntimeException("should not happen"); -// } double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; @@ -979,6 +958,7 @@ private boolean isNamedType(String type) { } // lower bound mapping cost between for v -> u in respect to a partial mapping + // this is BMa private double calcLowerBoundMappingCost(Vertex v, Vertex u, SchemaGraph sourceGraph, @@ -1035,6 +1015,7 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); + double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; return result; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 92d1dc220e..1c37b4630b 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -49,6 +49,28 @@ class SchemaDiffingTest extends Specification { then: diff.size() == 1 + } + def "test rename field 2"() { + given: + def schema1 = schema(""" + type Query { + fixed: String + hello: String + } + """) + def schema2 = schema(""" + type Query { + hello2: String + fixed: String + } + """) + + when: + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + diff.each { println it } + then: + diff.size() == 1 + } def "adding fields and rename and delete"() { From 374d962ce0efc450fa36ccc0e26773e9d93ff6b6 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 30 Sep 2022 15:19:46 +1000 Subject: [PATCH 136/294] cleanup --- .../diffing/FillupIsolatedVertices.java | 47 +- .../graphql/schema/diffing/SchemaDiffing.java | 448 +++--------------- .../graphql/schema/diffing/SchemaGraph.java | 14 + .../schema/diffing/SchemaDiffingTest.groovy | 55 ++- 4 files changed, 157 insertions(+), 407 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 748c0e33dd..6ce11a6d5b 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -1,8 +1,13 @@ package graphql.schema.diffing; +import com.google.common.collect.ArrayTable; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.HashBiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; +import com.google.common.collect.Table; import graphql.Assert; import graphql.util.FpKit; @@ -41,6 +46,8 @@ public class FillupIsolatedVertices { SchemaGraph targetGraph; IsolatedVertices isolatedVertices; + private BiMap toRemove = HashBiMap.create(); + static Map> typeContexts = new LinkedHashMap<>(); static { @@ -584,17 +591,23 @@ public void ensureGraphAreSameSize() { calcPossibleMappings(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT); calcPossibleMappings(typeContexts.get(DIRECTIVE), DIRECTIVE); +// for (Vertex sourceVertex : toRemove.keySet()) { +// sourceGraph.removeVertexAndEdges(sourceVertex); +// Vertex targetVertex = toRemove.get(sourceVertex); +// targetGraph.removeVertexAndEdges(targetVertex); +// } + sourceGraph.addVertices(isolatedVertices.allIsolatedSource); targetGraph.addVertices(isolatedVertices.allIsolatedTarget); Assert.assertTrue(sourceGraph.size() == targetGraph.size()); - for (Vertex vertex : isolatedVertices.possibleMappings.keySet()) { - Collection vertices = isolatedVertices.possibleMappings.get(vertex); - if (vertices.size() > 1) { - System.out.println("multiple for " + vertex); - } - } +// for (Vertex vertex : isolatedVertices.possibleMappings.keySet()) { +// Collection vertices = isolatedVertices.possibleMappings.get(vertex); +// if (vertices.size() > 1) { +// System.out.println("multiple for " + vertex); +// } +// } System.out.println("done isolated"); // if (sourceGraph.size() < targetGraph.size()) { // isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); @@ -633,6 +646,8 @@ public class IsolatedVertices { public Set allIsolatedSource = new LinkedHashSet<>(); public Set allIsolatedTarget = new LinkedHashSet<>(); + public Table, Set, Set> contexts = HashBasedTable.create(); + public final Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); public final Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); @@ -667,6 +682,13 @@ public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { return possibleMappings.containsEntry(sourceVertex, targetVertex); } + public void putContext(List contextId, Set source, Set target) { + if (contexts.containsRow(contextId)) { + throw new IllegalArgumentException("Already context " + contextId); + } + contexts.put(contextId, source, target); + } + } @@ -714,6 +736,7 @@ private void calcPossibleMappingImpl( for (String sameContext : sameContexts) { ImmutableList sourceVerticesInContext = sourceGroups.get(sameContext); ImmutableList targetVerticesInContext = targetGroups.get(sameContext); + List currentContextId = concat(contextId, sameContext); if (contexts.size() > contextIx + 1) { calcPossibleMappingImpl(sourceVerticesInContext, targetVerticesInContext, currentContextId, contextIx + 1, contexts, usedSourceVertices, usedTargetVertices, typeNameForDebug); @@ -727,6 +750,7 @@ private void calcPossibleMappingImpl( Set notUsedTarget = new LinkedHashSet<>(targetVerticesInContext); notUsedTarget.removeAll(usedTargetVertices); + // make sure the current context is the same size if (notUsedSource.size() > notUsedTarget.size()) { Set newTargetVertices = Vertex.newIsolatedNodes(notUsedSource.size() - notUsedTarget.size(), "target-isolated-" + typeNameForDebug + "-"); isolatedVertices.addIsolatedTarget(newTargetVertices); @@ -739,6 +763,9 @@ private void calcPossibleMappingImpl( isolatedVertices.putPossibleMappings(notUsedSource, notUsedTarget); usedSourceVertices.addAll(notUsedSource); usedTargetVertices.addAll(notUsedTarget); + if(notUsedSource.size() > 0 ) { + isolatedVertices.putContext(currentContextId, notUsedSource, notUsedTarget); + } } Set possibleTargetVertices = new LinkedHashSet<>(); @@ -772,8 +799,14 @@ private void calcPossibleMappingImpl( isolatedVertices.addIsolatedSource(newSourceVertices); possibleSourceVertices.addAll(newSourceVertices); } + // if there are only added or removed vertices in the current context, contextId might be empty + if(possibleSourceVertices.size() > 0) { + if(contextId.size() == 0) { + contextId = singletonList(typeNameForDebug); + } + isolatedVertices.putContext(contextId, possibleSourceVertices, possibleTargetVertices); + } isolatedVertices.putPossibleMappings(possibleSourceVertices, possibleTargetVertices); - } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1897f15690..57329b907a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -21,12 +22,11 @@ import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -159,7 +159,6 @@ public static void diffNamedList(Set sourceNames, List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); - PossibleMappings possibleMappings = new PossibleMappings(sourceGraph, targetGraph); FillupIsolatedVertices fillupIsolatedVertices = new FillupIsolatedVertices(sourceGraph, targetGraph); fillupIsolatedVertices.ensureGraphAreSameSize(); FillupIsolatedVertices.IsolatedVertices isolatedVertices = fillupIsolatedVertices.isolatedVertices; @@ -234,80 +233,80 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { - // we sort descending by number of possible target vertices - Collections.sort(sourceGraph.getVertices(), (v1, v2) -> - - { - - int v2Count = v2.isBuiltInType() ? -1 : (v2.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v2).size()); - int v1Count = v1.isBuiltInType() ? -1 : (v1.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v1).size()); - return Integer.compare(v2Count, v1Count); - }); - - for (Vertex vertex : sourceGraph.getVertices()) { - System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); - } - +// // we sort descending by number of possible target vertices +// Collections.sort(sourceGraph.getVertices(), (v1, v2) -> // +// { // -// // how often does each source edge (based on the label) appear in target graph -// Map targetLabelCount = new LinkedHashMap<>(); -// for (Edge targetEdge : targetGraph.getEdges()) { -// targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); -// } -// // how often does each source vertex (based on the data) appear in the target graph -// Map targetVertexDataCount = new LinkedHashMap<>(); -// for (Vertex targetVertex : targetGraph.getVertices()) { -// targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); -// } +// int v2Count = v2.isBuiltInType() ? -1 : (v2.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v2).size()); +// int v1Count = v1.isBuiltInType() ? -1 : (v1.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v1).size()); +// return Integer.compare(v2Count, v1Count); +// }); // -// // an infrequency weight is 1 - count in target. Meaning the higher the -// // value, the smaller the count, the less frequent it. -// // Higher Infrequency => more unique is the vertex/label -// Map vertexInfrequencyWeights = new LinkedHashMap<>(); -// Map edgesInfrequencyWeights = new LinkedHashMap<>(); // for (Vertex vertex : sourceGraph.getVertices()) { -// vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); +// System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); // } -// for (Edge edge : sourceGraph.getEdges()) { -// edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); -// } -// -// /** -// * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness -// * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. -// */ -// -// //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted -// ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); -// nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); -// -// Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); -// nextCandidates.remove(nextCandidates.size() - 1); + // -// List result = new ArrayList<>(); -// result.add(curVertex); -// while (nextCandidates.size() > 0) { -// Vertex nextOne = null; -// int curMaxWeight = Integer.MIN_VALUE; -// int index = 0; -// int nextOneIndex = -1; // -// // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices -// for (Vertex candidate : nextCandidates) { -// List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); -// int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); -// if (totalWeight > curMaxWeight) { -// nextOne = candidate; -// nextOneIndex = index; -// curMaxWeight = totalWeight; -// } -// index++; -// } -// result.add(nextOne); -// nextCandidates.remove(nextOneIndex); -// } -// sourceGraph.setVertices(result); +// // how often does each source edge (based on the label) appear in target graph + Map targetLabelCount = new LinkedHashMap<>(); + for (Edge targetEdge : targetGraph.getEdges()) { + targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); + } + // how often does each source vertex (based on the data) appear in the target graph + Map targetVertexDataCount = new LinkedHashMap<>(); + for (Vertex targetVertex : targetGraph.getVertices()) { + targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); + } + + // an infrequency weight is 1 - count in target. Meaning the higher the + // value, the smaller the count, the less frequent it. + // Higher Infrequency => more unique is the vertex/label + Map vertexInfrequencyWeights = new LinkedHashMap<>(); + Map edgesInfrequencyWeights = new LinkedHashMap<>(); + for (Vertex vertex : sourceGraph.getVertices()) { + vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); + } + for (Edge edge : sourceGraph.getEdges()) { + edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); + } + + /** + * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness + * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. + */ + + //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted + ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); + nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); + + Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); + nextCandidates.remove(nextCandidates.size() - 1); + + List result = new ArrayList<>(); + result.add(curVertex); + while (nextCandidates.size() > 0) { + Vertex nextOne = null; + int curMaxWeight = Integer.MIN_VALUE; + int index = 0; + int nextOneIndex = -1; + + // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices + for (Vertex candidate : nextCandidates) { + List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); + int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); + if (totalWeight > curMaxWeight) { + nextOne = candidate; + nextOneIndex = index; + curMaxWeight = totalWeight; + } + index++; + } + result.add(nextOne); + nextCandidates.remove(nextOneIndex); + } + sourceGraph.setVertices(result); } private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { @@ -650,313 +649,6 @@ private int editorialCostForMapping(Mapping partialOrFullMapping, } - private Map forcedMatchingCache = synchronizedMap(new LinkedHashMap<>()); - - private boolean isMappingPossible(Vertex v, - Vertex u, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - Set partialMappingTargetSet, - FillupIsolatedVertices.IsolatedVertices isolatedInfo - ) { - return isolatedInfo.mappingPossible(v, u); - -// Vertex forcedMatch = forcedMatchingCache.get(v); -// if (forcedMatch != null) { -// return forcedMatch == u; -// } -// if (v.isIsolated() && u.isIsolated()) { -// return false; -// } -// Set isolatedBuiltInSourceVertices = isolatedInfo.isolatedBuiltInSourceVertices; -// Set isolatedBuiltInTargetVertices = isolatedInfo.isolatedBuiltInTargetVertices; - -// if (v.isIsolated()) { -// if (u.isBuiltInType()) { -// return isolatedBuiltInSourceVertices.contains(v); -// } -// } -// } else { -// return isolatedInfo.mappingPossible(v, u); -//// if (u.getType().equals(FIELD)) { -//// Vertex fieldsContainer = targetGraph.getFieldsContainerForField(u); -//// String containerName = fieldsContainer.get("name"); -//// String fieldName = u.get("name"); -//// if (isolatedSourceVerticesForFields.contains(containerName, fieldName)) { -//// return isolatedSourceVerticesForFields.get(containerName, fieldName).contains(v); -//// } -//// } -//// if (u.getType().equals(INPUT_FIELD)) { -//// Vertex inputObject = targetGraph.getInputObjectForInputField(u); -//// String inputObjectName = inputObject.get("name"); -//// String fieldName = u.get("name"); -//// if (isolatedSourceVerticesForInputFields.contains(inputObjectName, fieldName)) { -//// return isolatedSourceVerticesForInputFields.get(inputObjectName, fieldName).contains(v); -//// } -//// } -//// return isolatedSourceVertices.getOrDefault(u.getType(), emptySet()).contains(v); -// } -// } -// if (u.isIsolated()) { -// if (v.isBuiltInType()) { -// return isolatedBuiltInTargetVertices.contains(u); -// } -// } -// } else { -// return isolatedInfo.mappingPossibleForIsolatedTarget(v, u); -//// if (v.getType().equals(FIELD)) { -//// Vertex fieldsContainer = sourceGraph.getFieldsContainerForField(v); -//// String containerName = fieldsContainer.get("name"); -//// String fieldName = v.get("name"); -//// if (isolatedTargetVerticesForFields.contains(containerName, fieldName)) { -//// return isolatedTargetVerticesForFields.get(containerName, fieldName).contains(u); -//// } -//// } -//// if (v.getType().equals(INPUT_FIELD)) { -//// Vertex inputObject = sourceGraph.getInputObjectForInputField(v); -//// String inputObjectName = inputObject.get("name"); -//// String fieldName = v.get("name"); -//// if (isolatedTargetVerticesForInputFields.contains(inputObjectName, fieldName)) { -//// return isolatedTargetVerticesForInputFields.get(inputObjectName, fieldName).contains(u); -//// } -//// } -//// return isolatedTargetVertices.getOrDefault(v.getType(), emptySet()).contains(u); -// } -// } -// // the types of the vertices need to match: we don't allow to change the type -// if (!v.getType().equals(u.getType())) { -// return false; -// } -// Boolean result = checkNamedTypes(v, u, targetGraph); -// if (result != null) { -// return result; -// } -// result = checkNamedTypes(u, v, sourceGraph); -// if (result != null) { -// return result; -// } -// result = checkSpecificTypes(v, u, sourceGraph, targetGraph); -// if (result != null) { -// return result; -// } -// result = checkSpecificTypes(u, v, targetGraph, sourceGraph); -// if (result != null) { -// return result; -// } -// -// return true; - } - - private Boolean checkSpecificTypes(Vertex v, Vertex u, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex matchingTargetVertex = findMatchingTargetVertex(v, sourceGraph, targetGraph); - if (matchingTargetVertex != null) { - forcedMatchingCache.put(v, matchingTargetVertex); - forcedMatchingCache.put(matchingTargetVertex, v); - return u == matchingTargetVertex; - } - return null; - - } - - private Vertex findMatchingTargetVertex(Vertex v, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - if (isNamedType(v.getType())) { - Vertex targetVertex = targetGraph.getType(v.get("name")); - // the type of the target vertex must match v - return targetVertex != null && targetVertex.getType().equals(v.getType()) ? targetVertex : null; - } - if (DIRECTIVE.equals(v.getType())) { - return targetGraph.getDirective(v.get("name")); - } - - if (DUMMY_TYPE_VERTEX.equals(v.getType())) { - List adjacentVertices = sourceGraph.getAdjacentVertices(v); - for (Vertex vertex : adjacentVertices) { - if (vertex.getType().equals(FIELD)) { - Vertex matchingTargetField = findMatchingTargetField(vertex, sourceGraph, targetGraph); - if (matchingTargetField != null) { - return getDummyTypeVertex(matchingTargetField, targetGraph); - } - } else if (vertex.getType().equals(INPUT_FIELD)) { - Vertex matchingTargetInputField = findMatchingTargetInputField(vertex, sourceGraph, targetGraph); - if (matchingTargetInputField != null) { - return getDummyTypeVertex(matchingTargetInputField, targetGraph); - } - } - } - return null; - } - if (INPUT_FIELD.equals(v.getType())) { - Vertex matchingTargetInputField = findMatchingTargetInputField(v, sourceGraph, targetGraph); - return matchingTargetInputField; - } - if (FIELD.equals(v.getType())) { - Vertex matchingTargetField = findMatchingTargetField(v, sourceGraph, targetGraph); - return matchingTargetField; - } - if (ENUM_VALUE.equals(v.getType())) { - Vertex matchingTargetEnumValue = findMatchingEnumValue(v, sourceGraph, targetGraph); - return matchingTargetEnumValue; - } - if (ARGUMENT.equals(v.getType())) { - Vertex matchingTargetArgument = findMatchingTargetArgument(v, sourceGraph, targetGraph); - return matchingTargetArgument; - } - - if (APPLIED_DIRECTIVE.equals(v.getType())) { - return findMatchingAppliedDirective(v, sourceGraph, targetGraph); - } - if (APPLIED_ARGUMENT.equals(v.getType())) { - return findMatchingAppliedArgument(v, sourceGraph, targetGraph); - } - - return Assert.assertShouldNeverHappen(); - } - - private Boolean checkNamedTypes(Vertex v, Vertex u, SchemaGraph targetGraph) { - if (isNamedType(v.getType())) { - Vertex targetVertex = targetGraph.getType(v.get("name")); - if (targetVertex != null && Objects.equals(v.getType(), targetVertex.getType())) { - forcedMatchingCache.put(v, targetVertex); - forcedMatchingCache.put(targetVertex, v); - return u == targetVertex; - } - } - return null; - } - - private Vertex getDummyTypeVertex(Vertex vertex, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(vertex, v -> v.getType().equals(DUMMY_TYPE_VERTEX)); - assertTrue(adjacentVertices.size() == 1); - return adjacentVertices.get(0); - } - - private Vertex findMatchingEnumValue(Vertex enumValue, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex enumVertex = getEnum(enumValue, sourceGraph); - Vertex targetEnumWithSameName = targetGraph.getType(enumVertex.get("name")); - if (targetEnumWithSameName != null) { - Vertex matchingTarget = getEnumValue(targetEnumWithSameName, enumValue.get("name"), targetGraph); - return matchingTarget; - } - return null; - } - - private Vertex findMatchingTargetInputField(Vertex inputField, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex sourceInputObject = getInputFieldsObject(inputField, sourceGraph); - Vertex targetInputObject = targetGraph.getType(sourceInputObject.get("name")); - if (targetInputObject != null) { - Vertex matchingInputField = getInputField(targetInputObject, inputField.get("name"), targetGraph); - return matchingInputField; - } - return null; - - } - - private Vertex findMatchingTargetArgument(Vertex argument, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex fieldOrDirective = getFieldOrDirectiveForArgument(argument, sourceGraph); - if (FIELD.equals(fieldOrDirective.getType())) { - Vertex matchingTargetField = findMatchingTargetField(fieldOrDirective, sourceGraph, targetGraph); - if (matchingTargetField != null) { - return getArgumentForFieldOrDirective(matchingTargetField, argument.get("name"), targetGraph); - } - } else { - // we have an Argument for a Directive - Vertex matchingDirective = targetGraph.getDirective(fieldOrDirective.get("name")); - if (matchingDirective != null) { - return getArgumentForFieldOrDirective(matchingDirective, argument.get("name"), targetGraph); - } - } - return null; - } - - private Vertex findMatchingAppliedDirective(Vertex appliedDirective, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex targetDirectiveWithSameName = targetGraph.getDirective(appliedDirective.get("name")); - if (targetDirectiveWithSameName == null) { - return null; - } - List adjacentVertices = sourceGraph.getAdjacentVertices(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); - assertTrue(adjacentVertices.size() == 1); - Vertex elementDirectiveAppliedTo = adjacentVertices.get(0); - //TODO: handle repeatable directives - Vertex targetMatchingAppliedTo = findMatchingTargetVertex(elementDirectiveAppliedTo, sourceGraph, targetGraph); - if (targetMatchingAppliedTo == null) { - return null; - } - List targetAppliedDirectives = targetGraph.getAdjacentVertices(targetMatchingAppliedTo, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE) && vertex.get("name").equals(appliedDirective.get("name"))); - return targetAppliedDirectives.size() == 0 ? null : targetAppliedDirectives.get(0); - } - - private Vertex findMatchingAppliedArgument(Vertex appliedArgument, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - List appliedDirectives = sourceGraph.getAdjacentVertices(appliedArgument, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE)); - assertTrue(appliedDirectives.size() == 1); - Vertex appliedDirective = appliedDirectives.get(0); - Vertex matchingAppliedDirective = findMatchingAppliedDirective(appliedDirective, sourceGraph, targetGraph); - if (matchingAppliedDirective == null) { - return null; - } - List matchingAppliedArguments = targetGraph.getAdjacentVertices(matchingAppliedDirective, vertex -> vertex.getType().equals(APPLIED_ARGUMENT) && vertex.get("name").equals(appliedArgument.get("name"))); - assertTrue(matchingAppliedArguments.size() <= 1); - return matchingAppliedArguments.size() == 0 ? null : matchingAppliedArguments.get(0); - - } - - private Vertex findMatchingTargetField(Vertex field, SchemaGraph sourceGraph, SchemaGraph targetGraph) { - Vertex sourceFieldsContainer = sourceGraph.getFieldsContainerForField(field); - Vertex targetFieldsContainerWithSameName = targetGraph.getType(sourceFieldsContainer.get("name")); - if (targetFieldsContainerWithSameName != null && targetFieldsContainerWithSameName.getType().equals(sourceFieldsContainer.getType())) { - Vertex matchingField = getFieldForContainer(targetFieldsContainerWithSameName, field.get("name"), targetGraph); - return matchingField; - } - return null; - } - - private Vertex getInputField(Vertex inputObject, String fieldName, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(inputObject, v -> v.getType().equals(INPUT_FIELD) && fieldName.equals(v.get("name"))); - assertTrue(adjacentVertices.size() <= 1); - return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); - } - - private Vertex getFieldForContainer(Vertex fieldsContainer, String fieldName, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(fieldsContainer, v -> v.getType().equals(FIELD) && fieldName.equals(v.get("name"))); - assertTrue(adjacentVertices.size() <= 1); - return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); - } - - private Vertex getEnumValue(Vertex enumVertex, String valueName, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(enumVertex, v -> valueName.equals(v.get("name"))); - assertTrue(adjacentVertices.size() <= 1); - return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); - } - - private Vertex getInputFieldsObject(Vertex inputField, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); - assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", inputField)); - return adjacentVertices.get(0); - } - - private Vertex getFieldOrDirectiveForArgument(Vertex argument, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(argument, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(DIRECTIVE)); - assertTrue(adjacentVertices.size() == 1, () -> format("No field or directive found for %s", argument)); - return adjacentVertices.get(0); - } - - private Vertex getArgumentForFieldOrDirective(Vertex fieldOrDirective, String argumentName, SchemaGraph schemaGraph) { - assertTrue(DIRECTIVE.equals(fieldOrDirective.getType()) || FIELD.equals(fieldOrDirective.getType())); - List adjacentVertices = schemaGraph.getAdjacentVertices(fieldOrDirective, vertex -> vertex.getType().equals(ARGUMENT) && vertex.get("name").equals(argumentName)); - assertTrue(adjacentVertices.size() <= 1); - return adjacentVertices.size() == 0 ? null : adjacentVertices.get(0); - } - - - private Vertex getEnum(Vertex enumValue, SchemaGraph schemaGraph) { - List adjacentVertices = schemaGraph.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); - assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for value %s", enumValue)); - return adjacentVertices.get(0); - } - - private boolean isNamedType(String type) { - return Arrays.asList(OBJECT, INTERFACE, INPUT_OBJECT, ENUM, UNION, SCALAR).contains(type); - } - // lower bound mapping cost between for v -> u in respect to a partial mapping // this is BMa private double calcLowerBoundMappingCost(Vertex v, @@ -970,7 +662,7 @@ private double calcLowerBoundMappingCost(Vertex v, FillupIsolatedVertices.IsolatedVertices isolatedInfo ) { - if (!isMappingPossible(v, u, sourceGraph, targetGraph, partialMappingTargetSet, isolatedInfo)) { + if (!isolatedInfo.mappingPossible(v, u)) { return Integer.MAX_VALUE; } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 7d27ba7ad8..bc16376914 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -80,6 +81,19 @@ public void addVertex(Vertex vertex) { typeToVertices.put(vertex.getType(), vertex); } + public void removeVertexAndEdges(Vertex vertex) { + vertices.remove(vertex); + typeToVertices.remove(vertex.getType(), vertex); + for (Iterator it = edges.iterator(); it.hasNext(); ) { + Edge edge = it.next(); + if (edge.getOne().equals(vertex) || edge.getTwo().equals(vertex)) { + edgeByVertexPair.remove(edge.getOne(), edge.getTwo()); + edgeByVertexPair.remove(edge.getTwo(), edge.getOne()); + it.remove(); + } + } + } + public void addVertices(Collection vertices) { for (Vertex vertex : vertices) { this.vertices.add(vertex); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 1c37b4630b..c56a06129b 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -7,6 +7,7 @@ import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer import graphql.util.TraversalControl import graphql.util.TraverserContext +import org.junit.Ignore import spock.lang.Specification import static graphql.TestUtil.schema @@ -56,13 +57,22 @@ class SchemaDiffingTest extends Specification { type Query { fixed: String hello: String + f3(arg3: String): String } + type O1 { + f1(arg1: String, x: String): String + } + """) def schema2 = schema(""" type Query { hello2: String fixed: String + f3(arg4: String): String } + type O2 { + f2(arg2: String, y: String): String + } """) when: @@ -405,28 +415,29 @@ class SchemaDiffingTest extends Specification { diff.size() == 171 } - def "change large schema a bit 2"() { - given: - def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) - int counter = 0; - def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { - @Override - TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { - if (fieldDefinition.getName() == "field50") { - counter++; - return deleteNode(context); - } - return TraversalControl.CONTINUE - } - }) - println "deleted fields: " + counter - when: - def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) - diff.each { println it } - then: - // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 - diff.size() == 5 * 171 - } +// @spock.lang.Ignore +// def "change large schema a bit 2"() { +// given: +// def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) +// int counter = 0; +// def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { +// @Override +// TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { +// if (fieldDefinition.getName() == "field50") { +// counter++; +// return deleteNode(context); +// } +// return TraversalControl.CONTINUE +// } +// }) +// println "deleted fields: " + counter +// when: +// def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) +// diff.each { println it } +// then: +// // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 +// diff.size() == 5 * 171 +// } def "change object type name used twice"() { given: From a06a5bcf41219f6023cda2abf6b6c1327b41a3b0 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 30 Sep 2022 15:27:46 +1000 Subject: [PATCH 137/294] cleanup --- .../diffing/FillupIsolatedVertices.java | 3 +- .../graphql/schema/diffing/SchemaDiffing.java | 97 +------------------ .../graphql/schema/diffing/SchemaGraph.java | 13 +++ .../java/graphql/schema/diffing/Util.java | 27 ++++++ .../schema/diffing/SchemaDiffingTest.groovy | 2 +- 5 files changed, 43 insertions(+), 99 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/Util.java diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 6ce11a6d5b..a06d1df336 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.Set; -import static graphql.schema.diffing.SchemaDiffing.diffNamedList; import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT; import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; import static graphql.schema.diffing.SchemaGraph.ARGUMENT; @@ -730,7 +729,7 @@ private void calcPossibleMappingImpl( List deletedContexts = new ArrayList<>(); List insertedContexts = new ArrayList<>(); List sameContexts = new ArrayList<>(); - diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); + Util.diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts); // for each unchanged context we descend recursively into for (String sameContext : sameContexts) { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 57329b907a..5e2ab1b085 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -6,14 +6,12 @@ import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; -import graphql.Assert; import graphql.schema.GraphQLSchema; import graphql.util.FpKit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -31,22 +29,6 @@ import java.util.function.Function; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT; -import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; -import static graphql.schema.diffing.SchemaGraph.ARGUMENT; -import static graphql.schema.diffing.SchemaGraph.DIRECTIVE; -import static graphql.schema.diffing.SchemaGraph.DUMMY_TYPE_VERTEX; -import static graphql.schema.diffing.SchemaGraph.ENUM; -import static graphql.schema.diffing.SchemaGraph.ENUM_VALUE; -import static graphql.schema.diffing.SchemaGraph.FIELD; -import static graphql.schema.diffing.SchemaGraph.INPUT_FIELD; -import static graphql.schema.diffing.SchemaGraph.INPUT_OBJECT; -import static graphql.schema.diffing.SchemaGraph.INTERFACE; -import static graphql.schema.diffing.SchemaGraph.OBJECT; -import static graphql.schema.diffing.SchemaGraph.SCALAR; -import static graphql.schema.diffing.SchemaGraph.UNION; -import static java.lang.String.format; -import static java.util.Collections.synchronizedMap; public class SchemaDiffing { @@ -89,72 +71,6 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); } - public static void diffNamedVertices(Collection sourceVertices, - Collection targetVertices, - List deleted, // sourceVertices - List inserted, // targetVertices - BiMap same) { - Map sourceByName = FpKit.groupingByUniqueKey(sourceVertices, Vertex::getName); - Map targetByName = FpKit.groupingByUniqueKey(targetVertices, Vertex::getName); - for (Vertex sourceVertex : sourceVertices) { - Vertex targetVertex = targetByName.get(sourceVertex.getName()); - if (targetVertex == null) { - deleted.add(sourceVertex); - } else { - same.put(sourceVertex, targetVertex); - } - } - - for (Vertex targetVertex : targetVertices) { - if (sourceByName.get((String) targetVertex.get("name")) == null) { - inserted.add(targetVertex); - } - } - } - - public static void diffVertices(Collection sourceVertices, - Collection targetVertices, - List deleted, // sourceVertices - List inserted, // targetVertices - BiMap same, - Function keyFn) { - Map sourceByKey = FpKit.groupingByUniqueKey(sourceVertices, keyFn); - Map targetByKey = FpKit.groupingByUniqueKey(targetVertices, keyFn); - for (Vertex sourceVertex : sourceVertices) { - Vertex targetVertex = targetByKey.get(keyFn.apply(sourceVertex)); - if (targetVertex == null) { - deleted.add(sourceVertex); - } else { - same.put(sourceVertex, targetVertex); - } - } - - for (Vertex targetVertex : targetVertices) { - if (sourceByKey.get(keyFn.apply(targetVertex)) == null) { - inserted.add(targetVertex); - } - } - } - - public static void diffNamedList(Set sourceNames, - Set targetNames, - List deleted, - List inserted, - List same) { - for (String sourceName : sourceNames) { - if (targetNames.contains(sourceName)) { - same.add(sourceName); - } else { - deleted.add(sourceName); - } - } - - for (String targetName : targetNames) { - if (!sourceNames.contains(targetName)) { - inserted.add(targetName); - } - } - } List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); @@ -294,7 +210,7 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, F // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices for (Vertex candidate : nextCandidates) { - List allAdjacentEdges = allAdjacentEdges(sourceGraph, result, candidate); + List allAdjacentEdges = sourceGraph.getAllAdjacentEdges( result, candidate); int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); if (totalWeight > curMaxWeight) { nextOne = candidate; @@ -309,17 +225,6 @@ private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, F sourceGraph.setVertices(result); } - private List allAdjacentEdges(SchemaGraph schemaGraph, List fromList, Vertex to) { - List result = new ArrayList<>(); - for (Vertex from : fromList) { - Edge edge = schemaGraph.getEdge(from, to); - if (edge == null) { - continue; - } - result.add(edge); - } - return result; - } private int totalInfrequencyWeightWithSomeEdges(Vertex vertex, List edges, diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index bc16376914..efda7f52dc 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -273,4 +273,17 @@ public Vertex getFieldOrInputFieldForDummyType(Vertex enumValue) { assertTrue(adjacentVertices.size() == 1, () -> format("No field or input field found for %s", enumValue)); return adjacentVertices.get(0); } + + public List getAllAdjacentEdges(List fromList, Vertex to) { + List result = new ArrayList<>(); + for (Vertex from : fromList) { + Edge edge = getEdge(from, to); + if (edge == null) { + continue; + } + result.add(edge); + } + return result; + } + } diff --git a/src/main/java/graphql/schema/diffing/Util.java b/src/main/java/graphql/schema/diffing/Util.java new file mode 100644 index 0000000000..5ef5d29f3f --- /dev/null +++ b/src/main/java/graphql/schema/diffing/Util.java @@ -0,0 +1,27 @@ +package graphql.schema.diffing; + +import java.util.List; +import java.util.Set; + +public class Util { + public static void diffNamedList(Set sourceNames, + Set targetNames, + List deleted, + List inserted, + List same) { + for (String sourceName : sourceNames) { + if (targetNames.contains(sourceName)) { + same.add(sourceName); + } else { + deleted.add(sourceName); + } + } + + for (String targetName : targetNames) { + if (!sourceNames.contains(targetName)) { + inserted.add(targetName); + } + } + } + +} diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index c56a06129b..7501535443 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -79,7 +79,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) diff.each { println it } then: - diff.size() == 1 + diff.size() == 6 } From c91e79b66db31db2fc6c989675ea88d8e0c7a1d9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 30 Sep 2022 15:32:49 +1000 Subject: [PATCH 138/294] cleanup --- .../graphql/schema/diffing/SchemaDiffing.java | 172 ++---------------- .../schema/diffing/SortSourceGraph.java | 138 ++++++++++++++ .../schema/diffing/SchemaDiffingTest.groovy | 4 +- 3 files changed, 153 insertions(+), 161 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/SortSourceGraph.java diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 5e2ab1b085..ac32a71f16 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,32 +1,22 @@ package graphql.schema.diffing; -import com.google.common.collect.BiMap; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; -import graphql.util.FpKit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import static graphql.Assert.assertTrue; @@ -57,21 +47,14 @@ public MappingEntry() { SchemaGraph sourceGraph; SchemaGraph targetGraph; - ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); - ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2, boolean oldVersion) throws Exception { + public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph); } - public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { - return diffGraphQLSchema(graphQLSchema1, graphQLSchema2, false); - } - - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); @@ -85,7 +68,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t System.out.println("graph size: " + graphSize); if (sizeDiff != 0) { - sortSourceGraph(sourceGraph, targetGraph, isolatedVertices); + SortSourceGraph.sortSourceGraph(sourceGraph, targetGraph, isolatedVertices); } AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); @@ -148,130 +131,6 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t } - private void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { -// // we sort descending by number of possible target vertices -// Collections.sort(sourceGraph.getVertices(), (v1, v2) -> -// -// { -// -// int v2Count = v2.isBuiltInType() ? -1 : (v2.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v2).size()); -// int v1Count = v1.isBuiltInType() ? -1 : (v1.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v1).size()); -// return Integer.compare(v2Count, v1Count); -// }); -// -// for (Vertex vertex : sourceGraph.getVertices()) { -// System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); -// } - -// -// -// // how often does each source edge (based on the label) appear in target graph - Map targetLabelCount = new LinkedHashMap<>(); - for (Edge targetEdge : targetGraph.getEdges()) { - targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); - } - // how often does each source vertex (based on the data) appear in the target graph - Map targetVertexDataCount = new LinkedHashMap<>(); - for (Vertex targetVertex : targetGraph.getVertices()) { - targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); - } - - // an infrequency weight is 1 - count in target. Meaning the higher the - // value, the smaller the count, the less frequent it. - // Higher Infrequency => more unique is the vertex/label - Map vertexInfrequencyWeights = new LinkedHashMap<>(); - Map edgesInfrequencyWeights = new LinkedHashMap<>(); - for (Vertex vertex : sourceGraph.getVertices()) { - vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); - } - for (Edge edge : sourceGraph.getEdges()) { - edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); - } - - /** - * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness - * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. - */ - - //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted - ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); - nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); - - Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); - nextCandidates.remove(nextCandidates.size() - 1); - - List result = new ArrayList<>(); - result.add(curVertex); - while (nextCandidates.size() > 0) { - Vertex nextOne = null; - int curMaxWeight = Integer.MIN_VALUE; - int index = 0; - int nextOneIndex = -1; - - // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices - for (Vertex candidate : nextCandidates) { - List allAdjacentEdges = sourceGraph.getAllAdjacentEdges( result, candidate); - int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); - if (totalWeight > curMaxWeight) { - nextOne = candidate; - nextOneIndex = index; - curMaxWeight = totalWeight; - } - index++; - } - result.add(nextOne); - nextCandidates.remove(nextOneIndex); - } - sourceGraph.setVertices(result); - } - - - private int totalInfrequencyWeightWithSomeEdges(Vertex vertex, - List edges, - Map vertexInfrequencyWeights, - Map edgesInfrequencyWeights) { - if (vertex.isBuiltInType()) { - return Integer.MIN_VALUE + 1; - } - if (vertex.isIsolated()) { - return Integer.MIN_VALUE + 2; - } - return vertexInfrequencyWeights.get(vertex) + edges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); - } - - private int totalInfrequencyWeightWithAdjacentEdges(SchemaGraph sourceGraph, - Vertex vertex, - Map vertexInfrequencyWeights, - Map edgesInfrequencyWeights) { - if (vertex.isBuiltInType()) { - return Integer.MIN_VALUE + 1; - } - if (vertex.isIsolated()) { - return Integer.MIN_VALUE + 2; - } - List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); - return vertexInfrequencyWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); - } - - private int infrequencyWeightForVertex(Vertex sourceVertex, SchemaGraph targetGraph) { - int count = 0; - for (Vertex targetVertex : targetGraph.getVertices()) { - if (sourceVertex.isEqualTo(targetVertex)) { - count++; - } - } - return 1 - count; - } - - private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { - int count = 0; - for (Edge targetEdge : targetGraph.getEdges()) { - if (sourceEdge.isEqualTo(targetEdge)) { - count++; - } - } - return 1 - count; - } // level starts at 1 indicating the level in the search tree to look for the next mapping @@ -363,22 +222,17 @@ private void generateChildren(MappingEntry parentEntry, System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } - executorService.submit(new Runnable() { - @Override - public void run() { - calculateChildren( - availableTargetVertices, - hungarianAlgorithm, - costMatrixCopy, - editorialCostForMapping, - partialMapping, - v_i, - upperBound.get(), - level, - siblings - ); - } - }); + calculateChildren( + availableTargetVertices, + hungarianAlgorithm, + costMatrixCopy, + editorialCostForMapping, + partialMapping, + v_i, + upperBound.get(), + level, + siblings + ); } private void calculateChildren(List availableTargetVertices, diff --git a/src/main/java/graphql/schema/diffing/SortSourceGraph.java b/src/main/java/graphql/schema/diffing/SortSourceGraph.java new file mode 100644 index 0000000000..696b5558be --- /dev/null +++ b/src/main/java/graphql/schema/diffing/SortSourceGraph.java @@ -0,0 +1,138 @@ +package graphql.schema.diffing; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class SortSourceGraph { + + public static void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { +// // we sort descending by number of possible target vertices +// Collections.sort(sourceGraph.getVertices(), (v1, v2) -> +// +// { +// +// int v2Count = v2.isBuiltInType() ? -1 : (v2.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v2).size()); +// int v1Count = v1.isBuiltInType() ? -1 : (v1.isIsolated() ? 0 : isolatedVertices.possibleMappings.get(v1).size()); +// return Integer.compare(v2Count, v1Count); +// }); +// +// for (Vertex vertex : sourceGraph.getVertices()) { +// System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); +// } + +// +// +// // how often does each source edge (based on the label) appear in target graph + Map targetLabelCount = new LinkedHashMap<>(); + for (Edge targetEdge : targetGraph.getEdges()) { + targetLabelCount.computeIfAbsent(targetEdge.getLabel(), __ -> new AtomicInteger()).incrementAndGet(); + } + // how often does each source vertex (based on the data) appear in the target graph + Map targetVertexDataCount = new LinkedHashMap<>(); + for (Vertex targetVertex : targetGraph.getVertices()) { + targetVertexDataCount.computeIfAbsent(targetVertex.toData(), __ -> new AtomicInteger()).incrementAndGet(); + } + + // an infrequency weight is 1 - count in target. Meaning the higher the + // value, the smaller the count, the less frequent it. + // Higher Infrequency => more unique is the vertex/label + Map vertexInfrequencyWeights = new LinkedHashMap<>(); + Map edgesInfrequencyWeights = new LinkedHashMap<>(); + for (Vertex vertex : sourceGraph.getVertices()) { + vertexInfrequencyWeights.put(vertex, 1 - targetVertexDataCount.getOrDefault(vertex.toData(), new AtomicInteger()).get()); + } + for (Edge edge : sourceGraph.getEdges()) { + edgesInfrequencyWeights.put(edge, 1 - targetLabelCount.getOrDefault(edge.getLabel(), new AtomicInteger()).get()); + } + + /** + * vertices are sorted by increasing frequency/decreasing infrequency/decreasing uniqueness + * we start with the most unique/least frequent/most infrequent and add incrementally the next most infrequent. + */ + + //TODO: improve this: this is doing to much: we just want the max infrequent vertex, not all sorted + ArrayList nextCandidates = new ArrayList<>(sourceGraph.getVertices()); + nextCandidates.sort(Comparator.comparingInt(o -> totalInfrequencyWeightWithAdjacentEdges(sourceGraph, o, vertexInfrequencyWeights, edgesInfrequencyWeights))); + + Vertex curVertex = nextCandidates.get(nextCandidates.size() - 1); + nextCandidates.remove(nextCandidates.size() - 1); + + List result = new ArrayList<>(); + result.add(curVertex); + while (nextCandidates.size() > 0) { Vertex nextOne = null; + int curMaxWeight = Integer.MIN_VALUE; + int index = 0; + int nextOneIndex = -1; + + // which ones of the candidates has the highest infrequency weight relatively to the current result set of vertices + for (Vertex candidate : nextCandidates) { + List allAdjacentEdges = sourceGraph.getAllAdjacentEdges(result, candidate); + int totalWeight = totalInfrequencyWeightWithSomeEdges(candidate, allAdjacentEdges, vertexInfrequencyWeights, edgesInfrequencyWeights); + if (totalWeight > curMaxWeight) { + nextOne = candidate; + nextOneIndex = index; + curMaxWeight = totalWeight; + } + index++; + } + result.add(nextOne); + nextCandidates.remove(nextOneIndex); + } + sourceGraph.setVertices(result); + } + + + private static int totalInfrequencyWeightWithSomeEdges(Vertex vertex, + List edges, + Map vertexInfrequencyWeights, + Map edgesInfrequencyWeights) { + if (vertex.isBuiltInType()) { + return Integer.MIN_VALUE + 1; + } + if (vertex.isIsolated()) { + return Integer.MIN_VALUE + 2; + } + return vertexInfrequencyWeights.get(vertex) + edges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); + } + + private static int totalInfrequencyWeightWithAdjacentEdges(SchemaGraph sourceGraph, + Vertex vertex, + Map vertexInfrequencyWeights, + Map edgesInfrequencyWeights) { + if (vertex.isBuiltInType()) { + return Integer.MIN_VALUE + 1; + } + if (vertex.isIsolated()) { + return Integer.MIN_VALUE + 2; + } + List adjacentEdges = sourceGraph.getAdjacentEdges(vertex); + return vertexInfrequencyWeights.get(vertex) + adjacentEdges.stream().mapToInt(edgesInfrequencyWeights::get).sum(); + } + + private int infrequencyWeightForVertex(Vertex sourceVertex, SchemaGraph targetGraph) { + int count = 0; + for (Vertex targetVertex : targetGraph.getVertices()) { + if (sourceVertex.isEqualTo(targetVertex)) { + count++; + } + } + return 1 - count; + } + + private int infrequencyWeightForEdge(Edge sourceEdge, SchemaGraph targetGraph) { + int count = 0; + for (Edge targetEdge : targetGraph.getEdges()) { + if (sourceEdge.isEqualTo(targetEdge)) { + count++; + } + } + return 1 - count; + } + + + +} diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 7501535443..918cad747e 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -239,7 +239,7 @@ class SchemaDiffingTest extends Specification { """) when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: /** @@ -594,7 +594,7 @@ class SchemaDiffingTest extends Specification { """) when: - def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2, false) + def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) diff.each { println it } then: From 8efeedef85193e11ad9c6eaab15ece5ecc9524c5 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:05:52 +1100 Subject: [PATCH 139/294] Replace deprecated builders --- .../groovy/graphql/validation/SpecValidationSchema.java | 9 +++++---- src/test/groovy/graphql/validation/rules/Harness.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/validation/SpecValidationSchema.java b/src/test/groovy/graphql/validation/SpecValidationSchema.java index a59f502dbd..5a8a255c1a 100644 --- a/src/test/groovy/graphql/validation/SpecValidationSchema.java +++ b/src/test/groovy/graphql/validation/SpecValidationSchema.java @@ -112,9 +112,9 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .field(newFieldDefinition().name("nickname").type(Scalars.GraphQLString)) .field(newFieldDefinition().name("barkVolume").type(Scalars.GraphQLInt)) .field(newFieldDefinition().name("doesKnowCommand").type(nonNull(Scalars.GraphQLBoolean)) - .argument(singletonList(dogCommandArg))) + .arguments(singletonList(dogCommandArg))) .field(newFieldDefinition().name("isHousetrained").type(Scalars.GraphQLBoolean) - .argument(singletonList(atOtherHomesArg))) + .arguments(singletonList(atOtherHomesArg))) .field(newFieldDefinition().name("owner").type(human)) .withInterface(SpecValidationSchema.pet) .build(); @@ -125,7 +125,7 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .field(newFieldDefinition().name("nickname").type(Scalars.GraphQLString)) .field(newFieldDefinition().name("meowVolume").type(Scalars.GraphQLInt)) .field(newFieldDefinition().name("doesKnowCommand").type(nonNull(Scalars.GraphQLBoolean)) - .argument(singletonList(catCommandArg))) + .arguments(singletonList(catCommandArg))) .withInterface(SpecValidationSchema.pet) .build(); @@ -237,6 +237,7 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .additionalDirective(dogDirective) .additionalDirective(nonNullDirective) .additionalDirective(objectArgumentDirective) - .build(specValidationDictionary); + .additionalTypes(specValidationDictionary) + .build(); } diff --git a/src/test/groovy/graphql/validation/rules/Harness.java b/src/test/groovy/graphql/validation/rules/Harness.java index 6d751285c7..2b233b77d0 100644 --- a/src/test/groovy/graphql/validation/rules/Harness.java +++ b/src/test/groovy/graphql/validation/rules/Harness.java @@ -77,7 +77,7 @@ public class Harness { .argument(newArgument() .name("atOtherHomes") .type(GraphQLBoolean) - .defaultValue(true))) + .defaultValueProgrammatic(true))) .field(newFieldDefinition() .name("isAtLocation") .type(GraphQLBoolean) From b7d22961157a6d59e1fb5cf03a733c28e2175096 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:13:56 +1100 Subject: [PATCH 140/294] Update deprecated typeresolvers in Garfield schema --- src/test/groovy/graphql/GarfieldSchema.java | 58 +++++++++++-------- .../groovy/graphql/TypeReferenceSchema.java | 1 + 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/test/groovy/graphql/GarfieldSchema.java b/src/test/groovy/graphql/GarfieldSchema.java index 6c22d31ab4..1e82f7e091 100644 --- a/src/test/groovy/graphql/GarfieldSchema.java +++ b/src/test/groovy/graphql/GarfieldSchema.java @@ -1,6 +1,7 @@ package graphql; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; @@ -111,23 +112,24 @@ public List getFriends() { .field(newFieldDefinition() .name("name") .type(GraphQLString)) - .typeResolver(new TypeResolver() { - @Override - public GraphQLObjectType getType(TypeResolutionEnvironment env) { - if (env.getObject() instanceof Dog) { - return DogType; - } - if (env.getObject() instanceof Person) { - return PersonType; - } - if (env.getObject() instanceof Cat) { - return CatType; - } - return null; - } - }) .build(); + public static TypeResolver namedTypeResolver = new TypeResolver() { + @Override + public GraphQLObjectType getType(TypeResolutionEnvironment env) { + if (env.getObject() instanceof Dog) { + return DogType; + } + if (env.getObject() instanceof Person) { + return PersonType; + } + if (env.getObject() instanceof Cat) { + return CatType; + } + return null; + } + }; + public static GraphQLObjectType DogType = newObject() .name("Dog") .field(newFieldDefinition() @@ -154,17 +156,18 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .name("Pet") .possibleType(CatType) .possibleType(DogType) - .typeResolver(env -> { - if (env.getObject() instanceof Cat) { - return CatType; - } - if (env.getObject() instanceof Dog) { - return DogType; - } - return null; - }) .build(); + public static TypeResolver petTypeResolver = env -> { + if (env.getObject() instanceof Cat) { + return CatType; + } + if (env.getObject() instanceof Dog) { + return DogType; + } + return null; + }; + public static GraphQLObjectType PersonType = newObject() .name("Person") .field(newFieldDefinition() @@ -179,9 +182,14 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .withInterface(NamedType) .build(); + public static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver("Named", namedTypeResolver) + .typeResolver("Pet", petTypeResolver) + .build(); + public static GraphQLSchema GarfieldSchema = GraphQLSchema.newSchema() .query(PersonType) + .codeRegistry(codeRegistry) .build(); - } diff --git a/src/test/groovy/graphql/TypeReferenceSchema.java b/src/test/groovy/graphql/TypeReferenceSchema.java index 1fc323324f..37c48a2841 100644 --- a/src/test/groovy/graphql/TypeReferenceSchema.java +++ b/src/test/groovy/graphql/TypeReferenceSchema.java @@ -318,6 +318,7 @@ public Boolean parseLiteral(Object input) { public static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .typeResolver("Pet", new TypeResolverProxy()) .typeResolver("Addressable", new TypeResolverProxy()) + .typeResolver("Named", GarfieldSchema.namedTypeResolver) .build(); public static GraphQLSchema SchemaWithReferences = GraphQLSchema.newSchema() From 8fbdcf27ca51ebcf4abaa74b3686712473bbbb0f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:42:21 +1100 Subject: [PATCH 141/294] Update deprecated methods in SpecValidationSchema --- .../validation/SpecValidationSchema.java | 109 +++++++++++------- .../ValidateCustomDirectives.groovy | 1 + 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/test/groovy/graphql/validation/SpecValidationSchema.java b/src/test/groovy/graphql/validation/SpecValidationSchema.java index 5a8a255c1a..6e5e196133 100644 --- a/src/test/groovy/graphql/validation/SpecValidationSchema.java +++ b/src/test/groovy/graphql/validation/SpecValidationSchema.java @@ -1,8 +1,8 @@ package graphql.validation; import graphql.Scalars; -import graphql.TypeResolutionEnvironment; import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLInputObjectField; @@ -55,27 +55,6 @@ public class SpecValidationSchema { public static final GraphQLInterfaceType sentient = GraphQLInterfaceType.newInterface() .name("Sentient") .field(newFieldDefinition().name("name").type(nonNull(Scalars.GraphQLString))) - .typeResolver(new TypeResolver() { - @Override - public GraphQLObjectType getType(TypeResolutionEnvironment env) { - if (env.getObject() instanceof Human) return human; - if (env.getObject() instanceof Alien) return alien; - return null; - } - }) - .build(); - - public static final GraphQLInterfaceType pet = GraphQLInterfaceType.newInterface() - .name("Pet") - .field(newFieldDefinition().name("name").type(nonNull(Scalars.GraphQLString))) - .typeResolver(new TypeResolver() { - @Override - public GraphQLObjectType getType(TypeResolutionEnvironment env) { - if (env.getObject() instanceof Dog) return dog; - if (env.getObject() instanceof Cat) return cat; - return null; - } - }) .build(); public static final GraphQLObjectType human = GraphQLObjectType.newObject() @@ -91,6 +70,21 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .withInterface(SpecValidationSchema.sentient) .build(); + public static final TypeResolver sentientTypeResolver = env -> { + if (env.getObject() instanceof Human) { + return human; + } + if (env.getObject() instanceof Alien) { + return alien; + } + return null; + }; + + public static final GraphQLArgument catCommandArg = GraphQLArgument.newArgument() + .name("catCommand") + .type(nonNull(catCommand)) + .build(); + public static final GraphQLArgument dogCommandArg = GraphQLArgument.newArgument() .name("dogCommand") .type(nonNull(dogCommand)) @@ -101,9 +95,9 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .type(Scalars.GraphQLBoolean) .build(); - public static final GraphQLArgument catCommandArg = GraphQLArgument.newArgument() - .name("catCommand") - .type(nonNull(catCommand)) + public static final GraphQLInterfaceType pet = GraphQLInterfaceType.newInterface() + .name("Pet") + .field(newFieldDefinition().name("name").type(nonNull(Scalars.GraphQLString))) .build(); public static final GraphQLObjectType dog = GraphQLObjectType.newObject() @@ -129,36 +123,61 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .withInterface(SpecValidationSchema.pet) .build(); + public static final TypeResolver petTypeResolver = env -> { + if (env.getObject() instanceof Dog) { + return dog; + } + if (env.getObject() instanceof Cat) { + return cat; + } + return null; + }; + public static final GraphQLUnionType catOrDog = GraphQLUnionType.newUnionType() .name("CatOrDog") .possibleTypes(cat, dog) - .typeResolver(env -> { - if (env.getObject() instanceof Cat) return cat; - if (env.getObject() instanceof Dog) return dog; - return null; - }) .build(); + public static final TypeResolver catOrDogTypeResolver = env -> { + if (env.getObject() instanceof Cat) { + return cat; + } + if (env.getObject() instanceof Dog) { + return dog; + } + return null; + }; + public static final GraphQLUnionType dogOrHuman = GraphQLUnionType.newUnionType() .name("DogOrHuman") .possibleTypes(dog, human) - .typeResolver(env -> { - if (env.getObject() instanceof Human) return human; - if (env.getObject() instanceof Dog) return dog; - return null; - }) .build(); + public static final TypeResolver dogOrHumanTypeResolver = env -> { + if (env.getObject() instanceof Human) { + return human; + } + if (env.getObject() instanceof Dog) { + return dog; + } + return null; + }; + public static final GraphQLUnionType humanOrAlien = GraphQLUnionType.newUnionType() .name("HumanOrAlien") .possibleTypes(human, alien) - .typeResolver(env -> { - if (env.getObject() instanceof Human) return human; - if (env.getObject() instanceof Alien) return alien; - return null; - }) .build(); + public static final TypeResolver humanOrAlienTypeResolver = env -> { + if (env.getObject() instanceof Human) { + return human; + } + if (env.getObject() instanceof Alien) { + return alien; + } + return null; + }; + public static final GraphQLDirective dogDirective = GraphQLDirective.newDirective() .name("dogDirective") .argument(newArgument().name("arg1").type(GraphQLString).build()) @@ -180,7 +199,6 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .field(newFieldDefinition().name("cat").type(cat)) .build(); - @SuppressWarnings("serial") public static final Set specValidationDictionary = new HashSet() {{ add(dogCommand); add(catCommand); @@ -229,8 +247,17 @@ public GraphQLObjectType getType(TypeResolutionEnvironment env) { .validLocations(FIELD, FRAGMENT_SPREAD, FRAGMENT_DEFINITION, INLINE_FRAGMENT, QUERY) .build(); + public static final GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() + .typeResolver("Sentient", sentientTypeResolver) + .typeResolver("Pet", petTypeResolver) + .typeResolver("CatOrDog", catOrDogTypeResolver) + .typeResolver("DogOrHuman", dogOrHumanTypeResolver) + .typeResolver("HumanOrAlien", humanOrAlienTypeResolver) + .build(); + public static final GraphQLSchema specValidationSchema = GraphQLSchema.newSchema() .query(queryRoot) + .codeRegistry(codeRegistry) .subscription(subscriptionRoot) .additionalDirective(upperDirective) .additionalDirective(lowerDirective) diff --git a/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy b/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy index f4f96470dc..5a1569e978 100644 --- a/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy +++ b/src/test/groovy/graphql/validation/ValidateCustomDirectives.groovy @@ -19,6 +19,7 @@ class ValidateCustomDirectives extends Specification { GraphQLSchema customDirectiveSchema = GraphQLSchema.newSchema() .query(SpecValidationSchema.queryRoot) + .codeRegistry(SpecValidationSchema.codeRegistry) .additionalDirective(SpecValidationSchema.dogDirective) .additionalDirective(customDirective) .additionalTypes(SpecValidationSchema.specValidationDictionary) From 27264320c07a39eb00fcd6f4addac339e82641e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dav=C3=A9?= Date: Tue, 4 Oct 2022 05:10:29 +0200 Subject: [PATCH 142/294] Update GraphQL Instrospection Spec Link (#2977) --- .../visibility/NoIntrospectionGraphqlFieldVisibility.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/visibility/NoIntrospectionGraphqlFieldVisibility.java b/src/main/java/graphql/schema/visibility/NoIntrospectionGraphqlFieldVisibility.java index 2fc2fe6f00..604e794114 100644 --- a/src/main/java/graphql/schema/visibility/NoIntrospectionGraphqlFieldVisibility.java +++ b/src/main/java/graphql/schema/visibility/NoIntrospectionGraphqlFieldVisibility.java @@ -10,8 +10,8 @@ /** * This field visibility will prevent Introspection queries from being performed. Technically this puts your - * system in contravention of the specification - http://facebook.github.io/graphql/#sec-Introspection but some - * production systems want this lock down in place. + * system in contravention of the specification + * but some production systems want this lock down in place. */ @PublicApi public class NoIntrospectionGraphqlFieldVisibility implements GraphqlFieldVisibility { From 73be22a59178145305407f86eb2ad224c4bc57bc Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 5 Oct 2022 08:55:39 +1000 Subject: [PATCH 143/294] test green --- .../DefaultGraphEditOperationAnalyzer.java | 192 +++---- .../java/graphql/schema/diffing/DiffImpl.java | 423 +++++++++++++++ .../graphql/schema/diffing/EditOperation.java | 51 +- .../diffing/FillupIsolatedVertices.java | 47 +- .../graphql/schema/diffing/SchemaDiffing.java | 498 ++++-------------- .../schema/diffing/SchemaDiffingTest.groovy | 45 +- 6 files changed, 709 insertions(+), 547 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/DiffImpl.java diff --git a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java index cf2e3f9747..5c0157a46a 100644 --- a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java @@ -1,96 +1,96 @@ -package graphql.schema.diffing; - -import graphql.Assert; -import graphql.schema.GraphQLSchema; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import static graphql.Assert.assertNotNull; - -/** - * Higher level GraphQL semantic assigned to - */ -public class DefaultGraphEditOperationAnalyzer { - - private GraphQLSchema oldSchema; - private GraphQLSchema newSchema; - private SchemaGraph oldSchemaGraph; - private SchemaGraph newSchemaGraph; - private SchemaChangedHandler schemaChangedHandler; - - public DefaultGraphEditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, SchemaGraph oldSchemaGraph, SchemaGraph newSchemaGraph, SchemaChangedHandler schemaChangedHandler) { - this.oldSchema = oldSchema; - this.newSchema = newSchema; - this.oldSchemaGraph = oldSchemaGraph; - this.newSchemaGraph = newSchemaGraph; - this.schemaChangedHandler = schemaChangedHandler; - } - - public void analyzeEdits(List editOperations) { - for (EditOperation editOperation : editOperations) { - // 5 edit operations - if (editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { - Vertex deletedVertex = editOperation.getDetails(); - if (!deletedVertex.getType().equals("Field")) { - continue; - } - String fieldName = deletedVertex.getProperty("name"); - // find the "dummy-type" vertex for this field - Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraph.DUMMY_TYPE_VERTEX))); - Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); - - Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> - edge.getOne().getType().equals("Object") || edge.getOne().getType().equals("Interface") || - edge.getTwo().getType().equals("Object") || edge.getTwo().getType().equals("Interface") - )); - Edge edgeFromDummyTypeToType = Assert.assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(dummyTypeVertex, edge -> edge != edgeToDummyTypeVertex)); - - List relatedEditOperations = searchForOperations(editOperations, Arrays.asList( - eo -> { - if (eo.getOperation() == EditOperation.Operation.DELETE_VERTEX) { - return eo.getDetails() == dummyTypeVertex; - } - return false; - }, - eo -> { - if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { - return eo.getDetails() == edgeToObjectOrInterface; - } - return false; - }, - eo -> { - if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { - return eo.getDetails() == edgeToDummyTypeVertex; - } - return false; - }, - eo -> { - if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { - return eo.getDetails() == edgeFromDummyTypeToType; - } - return false; - }) - ); - if (relatedEditOperations.size() == 4) { - schemaChangedHandler.fieldRemoved("Field " + edgeToObjectOrInterface.getOne().get("name") + "." + fieldName + " removed"); - } - } - } - } - - private List searchForOperations(List editOperations, List> predicates) { - List result = new ArrayList<>(); - for (EditOperation editOperation : editOperations) { - for (Predicate predicate : predicates) { - if (predicate.test(editOperation)) { - result.add(editOperation); - } - } - } - return result; - } - -} +//package graphql.schema.diffing; +// +//import graphql.Assert; +//import graphql.schema.GraphQLSchema; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +//import java.util.function.Predicate; +// +//import static graphql.Assert.assertNotNull; +// +///** +// * Higher level GraphQL semantic assigned to +// */ +//public class DefaultGraphEditOperationAnalyzer { +// +// private GraphQLSchema oldSchema; +// private GraphQLSchema newSchema; +// private SchemaGraph oldSchemaGraph; +// private SchemaGraph newSchemaGraph; +// private SchemaChangedHandler schemaChangedHandler; +// +// public DefaultGraphEditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, SchemaGraph oldSchemaGraph, SchemaGraph newSchemaGraph, SchemaChangedHandler schemaChangedHandler) { +// this.oldSchema = oldSchema; +// this.newSchema = newSchema; +// this.oldSchemaGraph = oldSchemaGraph; +// this.newSchemaGraph = newSchemaGraph; +// this.schemaChangedHandler = schemaChangedHandler; +// } +// +// public void analyzeEdits(List editOperations) { +// for (EditOperation editOperation : editOperations) { +// // 5 edit operations +// if (editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { +// Vertex deletedVertex = editOperation.getDetails(); +// if (!deletedVertex.getType().equals("Field")) { +// continue; +// } +// String fieldName = deletedVertex.getProperty("name"); +// // find the "dummy-type" vertex for this field +// Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraph.DUMMY_TYPE_VERTEX))); +// Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); +// +// Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> +// edge.getOne().getType().equals("Object") || edge.getOne().getType().equals("Interface") || +// edge.getTwo().getType().equals("Object") || edge.getTwo().getType().equals("Interface") +// )); +// Edge edgeFromDummyTypeToType = Assert.assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(dummyTypeVertex, edge -> edge != edgeToDummyTypeVertex)); +// +// List relatedEditOperations = searchForOperations(editOperations, Arrays.asList( +// eo -> { +// if (eo.getOperation() == EditOperation.Operation.DELETE_VERTEX) { +// return eo.getDetails() == dummyTypeVertex; +// } +// return false; +// }, +// eo -> { +// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { +// return eo.getDetails() == edgeToObjectOrInterface; +// } +// return false; +// }, +// eo -> { +// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { +// return eo.getDetails() == edgeToDummyTypeVertex; +// } +// return false; +// }, +// eo -> { +// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { +// return eo.getDetails() == edgeFromDummyTypeToType; +// } +// return false; +// }) +// ); +// if (relatedEditOperations.size() == 4) { +// schemaChangedHandler.fieldRemoved("Field " + edgeToObjectOrInterface.getOne().get("name") + "." + fieldName + " removed"); +// } +// } +// } +// } +// +// private List searchForOperations(List editOperations, List> predicates) { +// List result = new ArrayList<>(); +// for (EditOperation editOperation : editOperations) { +// for (Predicate predicate : predicates) { +// if (predicate.test(editOperation)) { +// result.add(editOperation); +// } +// } +// } +// return result; +// } +// +//} diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java new file mode 100644 index 0000000000..8a35323b1a --- /dev/null +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -0,0 +1,423 @@ +package graphql.schema.diffing; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multisets; +import com.google.common.util.concurrent.AtomicDouble; +import com.google.common.util.concurrent.AtomicDoubleArray; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicReference; + +import static graphql.Assert.assertTrue; + +public class DiffImpl { + + private static MappingEntry LAST_ELEMENT = new MappingEntry(); + private SchemaGraph completeSourceGraph; + private SchemaGraph completeTargetGraph; + + private static class MappingEntry { + public boolean siblingsFinished; + public LinkedBlockingQueue mappingEntriesSiblings; + public int[] assignments; + public List availableTargetVertices; + + + Mapping partialMapping = new Mapping(); + int level; + double lowerBoundCost; + + public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost) { + this.partialMapping = partialMapping; + this.level = level; + this.lowerBoundCost = lowerBoundCost; + } + + public MappingEntry() { + + } + } + + public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph) { + this.completeSourceGraph = completeSourceGraph; + this.completeTargetGraph = completeTargetGraph; + } + + List diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { + + AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); + AtomicReference bestFullMapping = new AtomicReference<>(); + AtomicReference> bestEdit = new AtomicReference<>(); + + int graphSize = relevantSourceList.size(); + + int mappingCost = editorialCostForMapping(startMapping, completeSourceGraph, completeTargetGraph, new ArrayList<>()); + int level = startMapping.size(); + MappingEntry firstMappingEntry = new MappingEntry(startMapping,level,mappingCost); + System.out.println("first entry: lower bound: " + mappingCost + " at level " + level ); + + PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { + int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); + if (compareResult == 0) { + return Integer.compare(mappingEntry2.level, mappingEntry1.level); + } else { + return compareResult; + } + }); + queue.add(firstMappingEntry); + firstMappingEntry.siblingsFinished = true; +// queue.add(new MappingEntry()); + int counter = 0; + while (!queue.isEmpty()) { + MappingEntry mappingEntry = queue.poll(); +// System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); +// if ((++counter) % 100 == 0) { +// System.out.println((counter) + " entry at level"); +// } + if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + continue; + } + if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { + getSibling( + mappingEntry.level, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + relevantSourceList, + relevantTargetList, + mappingEntry); + } + if (mappingEntry.level < graphSize) { + generateChildren(mappingEntry, + mappingEntry.level + 1, + queue, + upperBoundCost, + bestFullMapping, + bestEdit, + relevantSourceList, + relevantTargetList + ); + } + } + System.out.println("ged cost: " + upperBoundCost.doubleValue()); + + return bestEdit.get(); + + } + + + // level starts at 1 indicating the level in the search tree to look for the next mapping + private void generateChildren(MappingEntry parentEntry, + int level, + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference bestFullMapping, + AtomicReference> bestEdit, + List sourceList, + List targetList + + ) throws Exception { + Mapping partialMapping = parentEntry.partialMapping; + assertTrue(level - 1 == partialMapping.size()); + + // TODO: iterates over all target vertices + ArrayList availableTargetVertices = new ArrayList<>(targetList); + availableTargetVertices.removeAll(partialMapping.getTargets()); + assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); + // level starts at 1 ... therefore level - 1 is the current one we want to extend + Vertex v_i = sourceList.get(level - 1); + + + int costMatrixSize = sourceList.size() - level + 1; +// double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + + AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); + // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them + AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); +// + // we are skipping the first level -i indices + Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); + Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); + + + // costMatrix[0] is the row for v_i + for (int i = level - 1; i < sourceList.size(); i++) { + Vertex v = sourceList.get(i); + int j = 0; + for (Vertex u : availableTargetVertices) { + double cost = calcLowerBoundMappingCost(v, u, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); + costMatrix[i - level + 1].set(j, cost); + costMatrixCopy[i - level + 1].set(j, cost); + j++; + } + } + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); + + int[] assignments = hungarianAlgorithm.execute(); + int editorialCostForMapping = editorialCostForMapping(partialMapping, completeSourceGraph, completeTargetGraph, new ArrayList<>()); + double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); + + + double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; + int v_i_target_IndexSibling = assignments[0]; + Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); + Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); + + + if (lowerBoundForPartialMapping >= upperBound.doubleValue()) { + return; + } + MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMapping); + LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); +// newMappingEntry.siblingsReady = new AtomicBoolean(); + newMappingEntry.mappingEntriesSiblings = siblings; + newMappingEntry.assignments = assignments; + newMappingEntry.availableTargetVertices = availableTargetVertices; + + queue.add(newMappingEntry); + Mapping fullMapping = partialMapping.copy(); + for (int i = 0; i < assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + } + +// assertTrue(fullMapping.size() == sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); + if (costForFullMapping < upperBound.doubleValue()) { + upperBound.set(costForFullMapping); + bestFullMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + } + + calculateChildren( + availableTargetVertices, + hungarianAlgorithm, + costMatrixCopy, + editorialCostForMapping, + partialMapping, + v_i, + upperBound.get(), + level, + siblings + ); + } + + private void calculateChildren(List availableTargetVertices, + HungarianAlgorithm hungarianAlgorithm, + AtomicDoubleArray[] costMatrixCopy, + double editorialCostForMapping, + Mapping partialMapping, + Vertex v_i, + double upperBound, + int level, + LinkedBlockingQueue siblings + ) { + for (int child = 1; child < availableTargetVertices.size(); child++) { + int[] assignments = hungarianAlgorithm.nextChild(); + if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { + break; + } + + double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); + double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; + int v_i_target_IndexSibling = assignments[0]; + Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); + Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); + + + if (lowerBoundForPartialMappingSibling >= upperBound) { + break; + } + MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); + sibling.mappingEntriesSiblings = siblings; + sibling.assignments = assignments; + sibling.availableTargetVertices = availableTargetVertices; + + // first child we add to the queue, otherwise save it for later +// System.out.println("add child " + child); + siblings.add(sibling); + } + siblings.add(LAST_ELEMENT); + + } + + private void getSibling( + int level, + PriorityQueue queue, + AtomicDouble upperBoundCost, + AtomicReference bestFullMapping, + AtomicReference> bestEdit, + List sourceList, + List targetGraph, + MappingEntry mappingEntry) throws InterruptedException { + + MappingEntry sibling = mappingEntry.mappingEntriesSiblings.take(); + if (sibling == LAST_ELEMENT) { + mappingEntry.siblingsFinished = true; + return; + } + if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { +// System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); + + queue.add(sibling); + + // we need to start here from the parent mapping, this is why we remove the last element + Mapping fullMapping = sibling.partialMapping.removeLastElement(); + for (int i = 0; i < sibling.assignments.length; i++) { + fullMapping.add(sourceList.get(level - 1 + i), sibling.availableTargetVertices.get(sibling.assignments[i])); + } +// assertTrue(fullMapping.size() == this.sourceGraph.size()); + List editOperations = new ArrayList<>(); + int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); + if (costForFullMapping < upperBoundCost.doubleValue()) { + upperBoundCost.set(costForFullMapping); + bestFullMapping.set(fullMapping); + bestEdit.set(editOperations); + System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + + } + } else { +// System.out.println("sibling not good enough"); + } + } + + + private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { + double costMatrixSum = 0; + for (int i = 0; i < assignments.length; i++) { + costMatrixSum += costMatrix[i].get(assignments[i]); + } + return costMatrixSum; + } + + public static int editorialCostForMapping(Mapping partialOrFullMapping, + SchemaGraph sourceGraph, + SchemaGraph targetGraph, + List editOperationsResult) { + int cost = 0; + for (int i = 0; i < partialOrFullMapping.size(); i++) { + Vertex sourceVertex = partialOrFullMapping.getSource(i); + Vertex targetVertex = partialOrFullMapping.getTarget(i); + // Vertex changing (relabeling) + boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); + if (!equalNodes) { + if (sourceVertex.isIsolated()) { + editOperationsResult.add(EditOperation.insertVertex("Insert" + targetVertex, sourceVertex, targetVertex)); + } else if (targetVertex.isIsolated()) { + editOperationsResult.add(EditOperation.deleteVertex("Delete " + sourceVertex, sourceVertex, targetVertex)); + } else { + editOperationsResult.add(EditOperation.changeVertex("Change " + sourceVertex + " to " + targetVertex, sourceVertex, targetVertex)); + } + cost++; + } + } + List edges = sourceGraph.getEdges(); + // edge deletion or relabeling + for (Edge sourceEdge : edges) { + // only edges relevant to the subgraph + if (!partialOrFullMapping.containsSource(sourceEdge.getOne()) || !partialOrFullMapping.containsSource(sourceEdge.getTwo())) { + continue; + } + Vertex target1 = partialOrFullMapping.getTarget(sourceEdge.getOne()); + Vertex target2 = partialOrFullMapping.getTarget(sourceEdge.getTwo()); + Edge targetEdge = targetGraph.getEdge(target1, target2); + if (targetEdge == null) { + editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); + cost++; + } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { + editOperationsResult.add(EditOperation.changeEdge("Change " + sourceEdge + " to " + targetEdge, sourceEdge, targetEdge)); + cost++; + } + } + + //TODO: iterates over all edges in the target Graph + for (Edge targetEdge : targetGraph.getEdges()) { + // only subgraph edges + if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { + continue; + } + Vertex sourceFrom = partialOrFullMapping.getSource(targetEdge.getOne()); + Vertex sourceTo = partialOrFullMapping.getSource(targetEdge.getTwo()); + if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { + editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); + cost++; + } + } + return cost; + } + + + // lower bound mapping cost between for v -> u in respect to a partial mapping + // this is BMa + private double calcLowerBoundMappingCost(Vertex v, + Vertex u, + List partialMappingSourceList, + Set partialMappingSourceSet, + List partialMappingTargetList, + Set partialMappingTargetSet + + ) { +// if (!isolatedInfo.mappingPossible(v, u)) { +// return Integer.MAX_VALUE; +// } + boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); + + // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges + // which are adjacent of u (resp. v) which are inner edges + List adjacentEdgesV = completeSourceGraph.getAdjacentEdges(v); + Multiset multisetLabelsV = HashMultiset.create(); + + for (Edge edge : adjacentEdgesV) { + // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices + // or: at least one edge is part of the partial mapping + if (!partialMappingSourceSet.contains(edge.getOne()) && !partialMappingSourceSet.contains(edge.getTwo())) { + multisetLabelsV.add(edge.getLabel()); + } + } + + List adjacentEdgesU = completeTargetGraph.getAdjacentEdges(u); + Multiset multisetLabelsU = HashMultiset.create(); + for (Edge edge : adjacentEdgesU) { + // test if this is an inner edge + if (!partialMappingTargetSet.contains(edge.getOne()) && !partialMappingTargetSet.contains(edge.getTwo())) { + multisetLabelsU.add(edge.getLabel()); + } + } + + /** + * looking at all edges from x,vPrime and y,mappedVPrime + */ + int anchoredVerticesCost = 0; + for (int i = 0; i < partialMappingSourceList.size(); i++) { + Vertex vPrime = partialMappingSourceList.get(i); + Vertex mappedVPrime = partialMappingTargetList.get(i); + Edge sourceEdge = completeSourceGraph.getEdge(v, vPrime); + String labelSourceEdge = sourceEdge != null ? sourceEdge.getLabel() : null; + Edge targetEdge = completeTargetGraph.getEdge(u, mappedVPrime); + String labelTargetEdge = targetEdge != null ? targetEdge.getLabel() : null; + if (!Objects.equals(labelSourceEdge, labelTargetEdge)) { + anchoredVerticesCost++; + } + } + + Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); + int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); + + double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; + return result; + } + + +} diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index 00b25eda58..9b4f89f1c5 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -2,15 +2,51 @@ public class EditOperation { - public EditOperation(Operation operation, String description, Object details) { + private EditOperation(Operation operation, + String description, + Vertex sourceVertex, + Vertex targetVertex, + Edge sourceEdge, + Edge targetEdge) { this.operation = operation; this.description = description; - this.details = details; + this.sourceVertex = sourceVertex; + this.targetVertex = targetVertex; + this.sourceEdge = sourceEdge; + this.targetEdge = targetEdge; + } + + public static EditOperation deleteVertex(String description, Vertex sourceVertex,Vertex targetVertex) { + return new EditOperation(Operation.DELETE_VERTEX, description, sourceVertex, targetVertex, null, null); + } + + public static EditOperation insertVertex(String description,Vertex sourceVertex, Vertex targetVertex) { + return new EditOperation(Operation.INSERT_VERTEX, description, sourceVertex, targetVertex, null, null); + } + + public static EditOperation changeVertex(String description, Vertex sourceVertex, Vertex targetVertex) { + return new EditOperation(Operation.CHANGE_VERTEX, description, sourceVertex, targetVertex, null, null); + } + + public static EditOperation deleteEdge(String description, Edge sourceEdge) { + return new EditOperation(Operation.DELETE_EDGE, description, null, null, sourceEdge, null); + } + + public static EditOperation insertEdge(String description, Edge targetEdge) { + return new EditOperation(Operation.INSERT_EDGE, description, null, null, null, targetEdge); + } + + public static EditOperation changeEdge(String description, Edge sourceEdge, Edge targetEdge) { + return new EditOperation(Operation.CHANGE_EDGE, description, null, null, sourceEdge, targetEdge); } private Operation operation; private String description; - private Object details; + private Vertex sourceVertex; + private Vertex targetVertex; + private Edge sourceEdge; + private Edge targetEdge; + enum Operation { CHANGE_VERTEX, DELETE_VERTEX, INSERT_VERTEX, CHANGE_EDGE, INSERT_EDGE, DELETE_EDGE @@ -20,8 +56,13 @@ public Operation getOperation() { return operation; } - public T getDetails() { - return (T) details; + + public Vertex getSourceVertex() { + return sourceVertex; + } + + public Vertex getTargetVertex() { + return targetVertex; } @Override diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index a06d1df336..e3f1005f52 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -639,19 +639,16 @@ public VertexContextSegment(VertexContextSegment child) { public class IsolatedVertices { - public Multimap contextToIsolatedSourceVertices = HashMultimap.create(); - public Multimap contextToIsolatedTargetVertices = HashMultimap.create(); +// public Multimap contextToIsolatedSourceVertices = HashMultimap.create(); +// public Multimap contextToIsolatedTargetVertices = HashMultimap.create(); public Set allIsolatedSource = new LinkedHashSet<>(); public Set allIsolatedTarget = new LinkedHashSet<>(); public Table, Set, Set> contexts = HashBasedTable.create(); - public final Set isolatedBuiltInSourceVertices = new LinkedHashSet<>(); - public final Set isolatedBuiltInTargetVertices = new LinkedHashSet<>(); - - // from source to target public Multimap possibleMappings = HashMultimap.create(); + public Mapping mapping = new Mapping(); public void putPossibleMappings(Collection sourceVertices, Collection targetVertex) { for (Vertex sourceVertex : sourceVertices) { @@ -667,24 +664,28 @@ public void addIsolatedTarget(Collection isolatedTarget) { allIsolatedTarget.addAll(isolatedTarget); } - public void putSource(Object contextId, Collection isolatedSourcedVertices) { - contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); - allIsolatedSource.addAll(isolatedSourcedVertices); - } - - public void putTarget(Object contextId, Collection isolatedTargetVertices) { - contextToIsolatedTargetVertices.putAll(contextId, isolatedTargetVertices); - allIsolatedTarget.addAll(isolatedTargetVertices); - } - - public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { - return possibleMappings.containsEntry(sourceVertex, targetVertex); - } +// public void putSource(Object contextId, Collection isolatedSourcedVertices) { +// contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); +// allIsolatedSource.addAll(isolatedSourcedVertices); +// } +// +// public void putTarget(Object contextId, Collection isolatedTargetVertices) { +// contextToIsolatedTargetVertices.putAll(contextId, isolatedTargetVertices); +// allIsolatedTarget.addAll(isolatedTargetVertices); +// } +// +// public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { +// return possibleMappings.containsEntry(sourceVertex, targetVertex); +// } public void putContext(List contextId, Set source, Set target) { if (contexts.containsRow(contextId)) { throw new IllegalArgumentException("Already context " + contextId); } + Assert.assertTrue(source.size() == target.size()); + if (source.size() == 1) { + mapping.add(source.iterator().next(), target.iterator().next()); + } contexts.put(contextId, source, target); } @@ -762,7 +763,7 @@ private void calcPossibleMappingImpl( isolatedVertices.putPossibleMappings(notUsedSource, notUsedTarget); usedSourceVertices.addAll(notUsedSource); usedTargetVertices.addAll(notUsedTarget); - if(notUsedSource.size() > 0 ) { + if (notUsedSource.size() > 0) { isolatedVertices.putContext(currentContextId, notUsedSource, notUsedTarget); } } @@ -799,9 +800,9 @@ private void calcPossibleMappingImpl( possibleSourceVertices.addAll(newSourceVertices); } // if there are only added or removed vertices in the current context, contextId might be empty - if(possibleSourceVertices.size() > 0) { - if(contextId.size() == 0) { - contextId = singletonList(typeNameForDebug); + if (possibleSourceVertices.size() > 0) { + if (contextId.size() == 0) { + contextId = singletonList(typeNameForDebug); } isolatedVertices.putContext(contextId, possibleSourceVertices, possibleTargetVertices); } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index ac32a71f16..ff2b014bc2 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -22,28 +22,6 @@ public class SchemaDiffing { - private static MappingEntry LAST_ELEMENT = new MappingEntry(); - - private static class MappingEntry { - public boolean siblingsFinished; - public LinkedBlockingQueue mappingEntriesSiblings; - public int[] assignments; - public List availableTargetVertices; - - public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost) { - this.partialMapping = partialMapping; - this.level = level; - this.lowerBoundCost = lowerBoundCost; - } - - public MappingEntry() { - - } - - Mapping partialMapping = new Mapping(); - int level; - double lowerBoundCost; - } SchemaGraph sourceGraph; SchemaGraph targetGraph; @@ -63,413 +41,133 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t FillupIsolatedVertices.IsolatedVertices isolatedVertices = fillupIsolatedVertices.isolatedVertices; assertTrue(sourceGraph.size() == targetGraph.size()); - - int graphSize = sourceGraph.size(); - System.out.println("graph size: " + graphSize); - - if (sizeDiff != 0) { - SortSourceGraph.sortSourceGraph(sourceGraph, targetGraph, isolatedVertices); - } - - AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); - AtomicReference bestFullMapping = new AtomicReference<>(); - AtomicReference> bestEdit = new AtomicReference<>(); - - PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { - int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); - if (compareResult == 0) { - return Integer.compare(mappingEntry2.level, mappingEntry1.level); - } else { - return compareResult; - } - }); - queue.add(new MappingEntry()); - int counter = 0; - while (!queue.isEmpty()) { - MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); -// if ((++counter) % 100 == 0) { -// System.out.println((counter) + " entry at level"); -// } - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { - continue; - } - if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { - getSibling( - mappingEntry.level, - queue, - upperBoundCost, - bestFullMapping, - bestEdit, - sourceGraph, - targetGraph, - mappingEntry); - } - if (mappingEntry.level < graphSize) { - generateChildren(mappingEntry, - mappingEntry.level + 1, - queue, - upperBoundCost, - bestFullMapping, - bestEdit, - sourceGraph, - targetGraph, - isolatedVertices - ); - } - } - System.out.println("ged cost: " + upperBoundCost.doubleValue()); -// List debugMap = getDebugMap(bestFullMapping.get()); -// for (String debugLine : debugMap) { -// System.out.println(debugLine); +// if (sizeDiff != 0) { +// SortSourceGraph.sortSourceGraph(sourceGraph, targetGraph, isolatedVertices); // } -// System.out.println("edit : " + bestEdit); -// for (EditOperation editOperation : bestEdit.get()) { -// System.out.println(editOperation); -// } - return bestEdit.get(); - } - - - - - // level starts at 1 indicating the level in the search tree to look for the next mapping - private void generateChildren(MappingEntry parentEntry, - int level, - PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestFullMapping, - AtomicReference> bestEdit, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - FillupIsolatedVertices.IsolatedVertices isolatedInfo - - ) throws Exception { - Mapping partialMapping = parentEntry.partialMapping; - assertTrue(level - 1 == partialMapping.size()); - List sourceList = sourceGraph.getVertices(); - List targetList = targetGraph.getVertices(); - - // TODO: iterates over all target vertices - ArrayList availableTargetVertices = new ArrayList<>(targetList); - availableTargetVertices.removeAll(partialMapping.getTargets()); - assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); - // level starts at 1 ... therefore level - 1 is the current one we want to extend - Vertex v_i = sourceList.get(level - 1); - - - int costMatrixSize = sourceList.size() - level + 1; -// double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; - - AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; - Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); - // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them - AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; - Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); -// - // we are skipping the first level -i indices - Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); - Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); - - - // costMatrix[0] is the row for v_i - for (int i = level - 1; i < sourceList.size(); i++) { - Vertex v = sourceList.get(i); - int j = 0; - for (Vertex u : availableTargetVertices) { - double cost = calcLowerBoundMappingCost(v, u, sourceGraph, targetGraph, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet, isolatedInfo); - costMatrix[i - level + 1].set(j, cost); - costMatrixCopy[i - level + 1].set(j, cost); - j++; - } - } - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); - - int[] assignments = hungarianAlgorithm.execute(); - int editorialCostForMapping = editorialCostForMapping(partialMapping, sourceGraph, targetGraph, new ArrayList<>()); - double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); - - - double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; - int v_i_target_IndexSibling = assignments[0]; - Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); - Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - - - if (lowerBoundForPartialMapping >= upperBound.doubleValue()) { - return; - } - MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMapping); - LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); -// newMappingEntry.siblingsReady = new AtomicBoolean(); - newMappingEntry.mappingEntriesSiblings = siblings; - newMappingEntry.assignments = assignments; - newMappingEntry.availableTargetVertices = availableTargetVertices; - - queue.add(newMappingEntry); - Mapping fullMapping = partialMapping.copy(); - for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); - } - - assertTrue(fullMapping.size() == sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, sourceGraph, targetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestFullMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } - - calculateChildren( - availableTargetVertices, - hungarianAlgorithm, - costMatrixCopy, - editorialCostForMapping, - partialMapping, - v_i, - upperBound.get(), - level, - siblings - ); - } - - private void calculateChildren(List availableTargetVertices, - HungarianAlgorithm hungarianAlgorithm, - AtomicDoubleArray[] costMatrixCopy, - double editorialCostForMapping, - Mapping partialMapping, - Vertex v_i, - double upperBound, - int level, - LinkedBlockingQueue siblings - ) { - for (int child = 1; child < availableTargetVertices.size(); child++) { - int[] assignments = hungarianAlgorithm.nextChild(); - if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { - break; - } - - double costMatrixSumSibling = getCostMatrixSum(costMatrixCopy, assignments); - double lowerBoundForPartialMappingSibling = editorialCostForMapping + costMatrixSumSibling; - int v_i_target_IndexSibling = assignments[0]; - Vertex bestExtensionTargetVertexSibling = availableTargetVertices.get(v_i_target_IndexSibling); - Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - - - if (lowerBoundForPartialMappingSibling >= upperBound) { - break; - } - MappingEntry sibling = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMappingSibling); - sibling.mappingEntriesSiblings = siblings; - sibling.assignments = assignments; - sibling.availableTargetVertices = availableTargetVertices; - - // first child we add to the queue, otherwise save it for later -// System.out.println("add child " + child); - siblings.add(sibling); + Mapping fixedMappings = isolatedVertices.mapping; + System.out.println("fixed mappings: " + fixedMappings.size() + " vs " + sourceGraph.size()); + if (fixedMappings.size() == sourceGraph.size()) { + ArrayList result = new ArrayList<>(); + DiffImpl.editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); + return result; } - siblings.add(LAST_ELEMENT); - - } + DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph); + List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); + nonMappedSource.removeAll(fixedMappings.getSources()); - private void getSibling( - int level, - PriorityQueue queue, - AtomicDouble upperBoundCost, - AtomicReference bestFullMapping, - AtomicReference> bestEdit, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - MappingEntry mappingEntry) throws InterruptedException { + List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); + nonMappedTarget.removeAll(fixedMappings.getTargets()); - MappingEntry sibling = mappingEntry.mappingEntriesSiblings.take(); - if (sibling == LAST_ELEMENT) { - mappingEntry.siblingsFinished = true; - return; - } - if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { -// System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); + // the non mapped vertices go to the end + List sourceVertices = new ArrayList<>(); + sourceVertices.addAll(fixedMappings.getSources()); + sourceVertices.addAll(nonMappedSource); - queue.add(sibling); + List targetGraphVertices = new ArrayList<>(); + targetGraphVertices.addAll(fixedMappings.getTargets()); + targetGraphVertices.addAll(nonMappedTarget); - List sourceList = sourceGraph.getVertices(); - // we need to start here from the parent mapping, this is why we remove the last element - Mapping fullMapping = sibling.partialMapping.removeLastElement(); - for (int i = 0; i < sibling.assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), sibling.availableTargetVertices.get(sibling.assignments[i])); - } - assertTrue(fullMapping.size() == this.sourceGraph.size()); - List editOperations = new ArrayList<>(); - int costForFullMapping = editorialCostForMapping(fullMapping, this.sourceGraph, this.targetGraph, editOperations); - if (costForFullMapping < upperBoundCost.doubleValue()) { - upperBoundCost.set(costForFullMapping); - bestFullMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - } - } else { -// System.out.println("sibling not good enough"); - } - } + List editOperations = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); - private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { - double costMatrixSum = 0; - for (int i = 0; i < assignments.length; i++) { - costMatrixSum += costMatrix[i].get(assignments[i]); - } - return costMatrixSum; - } - - private void logUnmappable(AtomicDoubleArray[] costMatrix, int[] assignments, List sourceList, ArrayList availableTargetVertices, int level) { - for (int i = 0; i < assignments.length; i++) { - double value = costMatrix[i].get(assignments[i]); - if (value >= Integer.MAX_VALUE) { - System.out.println("i " + i + " can't mapped"); - Vertex v = sourceList.get(i + level - 1); - Vertex u = availableTargetVertices.get(assignments[i]); - System.out.println("from " + v + " to " + u); - } - } - } - - private List getDebugMap(Mapping mapping) { - List result = new ArrayList<>(); -// if (mapping.size() > 0) { -// result.add(mapping.getSource(mapping.size() - 1).getType() + " -> " + mapping.getTarget(mapping.size() - 1).getType()); -// } - for (Map.Entry entry : mapping.getMap().entrySet()) { -// if (!entry.getKey().getType().equals(entry.getValue().getType())) { -// result.add(entry.getKey().getType() + "->" + entry.getValue().getType()); +// Mapping overallMapping = new Mapping(); +// ArrayList overallEdits = new ArrayList<>(); +// +// for (List contextId : isolatedVertices.contexts.rowKeySet()) { +// Set sourceList = isolatedVertices.contexts.row(contextId).keySet().iterator().next(); +// Set targetList = isolatedVertices.contexts.get(contextId, sourceList); +// assertTrue(sourceList.size() == targetList.size()); +// System.out.println(); +// if (sourceList.size() == 1) { +// Vertex sourceVertex = sourceList.iterator().next(); +// Vertex targetVertex = targetList.iterator().next(); +// overallMapping.add(sourceVertex, targetVertex); +// continue; // } - result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); - } - return result; +// System.out.println("contextId: " + contextId + " with vertices: " + sourceList.size()); +// List editOperations = diffImpl.diffImpl(new ArrayList<>(sourceList), new ArrayList<>(targetList)); +// +// for (EditOperation editOperation : editOperations) { +// if (editOperation.getOperation() == EditOperation.Operation.CHANGE_VERTEX || +// editOperation.getOperation() == EditOperation.Operation.INSERT_VERTEX || +// editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { +// overallMapping.add(editOperation.getSourceVertex(), editOperation.getTargetVertex()); +// overallEdits.add(editOperation); +// } +// } +// } + +// List edgeOperations = calcEdgeOperations(overallMapping); +// overallEdits.addAll(edgeOperations); + return editOperations; } - // minimum number of edit operations for a full mapping - private int editorialCostForMapping(Mapping partialOrFullMapping, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - List editOperationsResult) { - int cost = 0; - for (int i = 0; i < partialOrFullMapping.size(); i++) { - Vertex sourceVertex = partialOrFullMapping.getSource(i); - Vertex targetVertex = partialOrFullMapping.getTarget(i); - // Vertex changing (relabeling) - boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); - if (!equalNodes) { - if (sourceVertex.isIsolated()) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_VERTEX, "Insert" + targetVertex, targetVertex)); - } else if (targetVertex.isIsolated()) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_VERTEX, "Delete " + sourceVertex, sourceVertex)); - } else { - editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_VERTEX, "Change " + sourceVertex + " to " + targetVertex, Arrays.asList(sourceVertex, targetVertex))); - } - cost++; - } - } + private List calcEdgeOperations(Mapping mapping) { List edges = sourceGraph.getEdges(); + List result = new ArrayList<>(); // edge deletion or relabeling for (Edge sourceEdge : edges) { - // only edges relevant to the subgraph - if (!partialOrFullMapping.containsSource(sourceEdge.getOne()) || !partialOrFullMapping.containsSource(sourceEdge.getTwo())) { - continue; - } - Vertex target1 = partialOrFullMapping.getTarget(sourceEdge.getOne()); - Vertex target2 = partialOrFullMapping.getTarget(sourceEdge.getTwo()); + Vertex target1 = mapping.getTarget(sourceEdge.getOne()); + Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); Edge targetEdge = targetGraph.getEdge(target1, target2); if (targetEdge == null) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.DELETE_EDGE, "Delete edge " + sourceEdge, sourceEdge)); - cost++; + result.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.CHANGE_EDGE, "Change " + sourceEdge + " to " + targetEdge, Arrays.asList(sourceEdge, targetEdge))); - cost++; + result.add(EditOperation.changeEdge("Change " + sourceEdge + " to " + targetEdge, sourceEdge, targetEdge)); } } //TODO: iterates over all edges in the target Graph for (Edge targetEdge : targetGraph.getEdges()) { // only subgraph edges - if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { - continue; - } - Vertex sourceFrom = partialOrFullMapping.getSource(targetEdge.getOne()); - Vertex sourceTo = partialOrFullMapping.getSource(targetEdge.getTwo()); + Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); + Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { - editOperationsResult.add(new EditOperation(EditOperation.Operation.INSERT_EDGE, "Insert edge " + targetEdge, targetEdge)); - cost++; + result.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); } } - return cost; - } - - - // lower bound mapping cost between for v -> u in respect to a partial mapping - // this is BMa - private double calcLowerBoundMappingCost(Vertex v, - Vertex u, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - List partialMappingSourceList, - Set partialMappingSourceSet, - List partialMappingTargetList, - Set partialMappingTargetSet, - FillupIsolatedVertices.IsolatedVertices isolatedInfo - - ) { - if (!isolatedInfo.mappingPossible(v, u)) { - return Integer.MAX_VALUE; - } - boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); - - // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges - // which are adjacent of u (resp. v) which are inner edges - List adjacentEdgesV = sourceGraph.getAdjacentEdges(v); - Multiset multisetLabelsV = HashMultiset.create(); - - for (Edge edge : adjacentEdgesV) { - // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices - // or: at least one edge is part of the partial mapping - if (!partialMappingSourceSet.contains(edge.getOne()) && !partialMappingSourceSet.contains(edge.getTwo())) { - multisetLabelsV.add(edge.getLabel()); - } - } - - List adjacentEdgesU = targetGraph.getAdjacentEdges(u); - Multiset multisetLabelsU = HashMultiset.create(); - for (Edge edge : adjacentEdgesU) { - // test if this is an inner edge - if (!partialMappingTargetSet.contains(edge.getOne()) && !partialMappingTargetSet.contains(edge.getTwo())) { - multisetLabelsU.add(edge.getLabel()); - } - } - - /** - * looking at all edges from x,vPrime and y,mappedVPrime - */ - int anchoredVerticesCost = 0; - for (int i = 0; i < partialMappingSourceList.size(); i++) { - Vertex vPrime = partialMappingSourceList.get(i); - Vertex mappedVPrime = partialMappingTargetList.get(i); - Edge sourceEdge = sourceGraph.getEdge(v, vPrime); - String labelSourceEdge = sourceEdge != null ? sourceEdge.getLabel() : null; - Edge targetEdge = targetGraph.getEdge(u, mappedVPrime); - String labelTargetEdge = targetEdge != null ? targetEdge.getLabel() : null; - if (!Objects.equals(labelSourceEdge, labelTargetEdge)) { - anchoredVerticesCost++; - } - } - - Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); - int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - - double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; return result; } +// List debugMap = getDebugMap(bestFullMapping.get()); +// for (String debugLine : debugMap) { +// System.out.println(debugLine); +// } +// System.out.println("edit : " + bestEdit); +// for (EditOperation editOperation : bestEdit.get()) { +// System.out.println(editOperation); +// } +// private List diffImplImpl() { + +// private void logUnmappable(AtomicDoubleArray[] costMatrix, int[] assignments, List sourceList, ArrayList availableTargetVertices, int level) { +// for (int i = 0; i < assignments.length; i++) { +// double value = costMatrix[i].get(assignments[i]); +// if (value >= Integer.MAX_VALUE) { +// System.out.println("i " + i + " can't mapped"); +// Vertex v = sourceList.get(i + level - 1); +// Vertex u = availableTargetVertices.get(assignments[i]); +// System.out.println("from " + v + " to " + u); +// } +// } +// } +// +// private List getDebugMap(Mapping mapping) { +// List result = new ArrayList<>(); +//// if (mapping.size() > 0) { +//// result.add(mapping.getSource(mapping.size() - 1).getType() + " -> " + mapping.getTarget(mapping.size() - 1).getType()); +//// } +// for (Map.Entry entry : mapping.getMap().entrySet()) { +//// if (!entry.getKey().getType().equals(entry.getValue().getType())) { +//// result.add(entry.getKey().getType() + "->" + entry.getValue().getType()); +//// } +// result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); +// } +// return result; +// } +// +// // minimum number of edit operations for a full mapping +// + } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 918cad747e..740cf58b3d 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -415,29 +415,28 @@ class SchemaDiffingTest extends Specification { diff.size() == 171 } -// @spock.lang.Ignore -// def "change large schema a bit 2"() { -// given: -// def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) -// int counter = 0; -// def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { -// @Override -// TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { -// if (fieldDefinition.getName() == "field50") { -// counter++; -// return deleteNode(context); -// } -// return TraversalControl.CONTINUE -// } -// }) -// println "deleted fields: " + counter -// when: -// def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) -// diff.each { println it } -// then: -// // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 -// diff.size() == 5 * 171 -// } + def "change large schema a bit 2"() { + given: + def largeSchema = TestUtil.schemaFromResource("large-schema-2.graphqls", TestUtil.mockRuntimeWiring) + int counter = 0; + def changedOne = SchemaTransformer.transformSchema(largeSchema, new GraphQLTypeVisitorStub() { + @Override + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition fieldDefinition, TraverserContext context) { + if (fieldDefinition.getName() == "field50") { + counter++; + return deleteNode(context); + } + return TraversalControl.CONTINUE + } + }) + println "deleted fields: " + counter + when: + def diff = new SchemaDiffing().diffGraphQLSchema(largeSchema, changedOne) + diff.each { println it } + then: + // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 + diff.size() == 5 * 171 + } def "change object type name used twice"() { given: From 9d9afd8c89e6eb1589c61ed814ddff5c2db834f5 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 5 Oct 2022 09:40:25 +1000 Subject: [PATCH 144/294] consider impossible mappings --- src/main/java/graphql/schema/diffing/DiffImpl.java | 10 ++++++---- .../graphql/schema/diffing/FillupIsolatedVertices.java | 6 +++--- .../java/graphql/schema/diffing/SchemaDiffing.java | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 8a35323b1a..a0b13bd0d9 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -23,6 +23,7 @@ public class DiffImpl { private static MappingEntry LAST_ELEMENT = new MappingEntry(); private SchemaGraph completeSourceGraph; private SchemaGraph completeTargetGraph; + private FillupIsolatedVertices.IsolatedVertices isolatedVertices; private static class MappingEntry { public boolean siblingsFinished; @@ -46,9 +47,10 @@ public MappingEntry() { } } - public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph) { + public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { this.completeSourceGraph = completeSourceGraph; this.completeTargetGraph = completeTargetGraph; + this.isolatedVertices = isolatedVertices; } List diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { @@ -369,9 +371,9 @@ private double calcLowerBoundMappingCost(Vertex v, Set partialMappingTargetSet ) { -// if (!isolatedInfo.mappingPossible(v, u)) { -// return Integer.MAX_VALUE; -// } + if (!isolatedVertices.mappingPossible(v, u)) { + return Integer.MAX_VALUE; + } boolean equalNodes = v.getType().equals(u.getType()) && v.getProperties().equals(u.getProperties()); // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index e3f1005f52..46aa776b9d 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -674,9 +674,9 @@ public void addIsolatedTarget(Collection isolatedTarget) { // allIsolatedTarget.addAll(isolatedTargetVertices); // } // -// public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { -// return possibleMappings.containsEntry(sourceVertex, targetVertex); -// } + public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { + return possibleMappings.containsEntry(sourceVertex, targetVertex); + } public void putContext(List contextId, Set source, Set target) { if (contexts.containsRow(contextId)) { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index ff2b014bc2..d6fd569a72 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -51,7 +51,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t DiffImpl.editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); return result; } - DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph); + DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); nonMappedSource.removeAll(fixedMappings.getSources()); From e3ea10f62732113d850cab77c9ef2cf93f348dbc Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 5 Oct 2022 14:02:18 +1000 Subject: [PATCH 145/294] sort source element --- .../graphql/schema/diffing/SchemaDiffing.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index d6fd569a72..a7e2393d26 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -58,6 +59,8 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); nonMappedTarget.removeAll(fixedMappings.getTargets()); + sortListBasedOnPossibleMapping(nonMappedSource, isolatedVertices); + // the non mapped vertices go to the end List sourceVertices = new ArrayList<>(); sourceVertices.addAll(fixedMappings.getSources()); @@ -103,6 +106,20 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t return editOperations; } + private void sortListBasedOnPossibleMapping(List sourceVertices, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { + Collections.sort(sourceVertices, (v1, v2) -> + { + int v2Count = isolatedVertices.possibleMappings.get(v2).size(); + int v1Count = isolatedVertices.possibleMappings.get(v1).size(); + return Integer.compare(v2Count, v1Count); + }); + +// for (Vertex vertex : sourceGraph.getVertices()) { +// System.out.println("c: " + isolatedVertices.possibleMappings.get(vertex).size() + " v: " + vertex); +// } + } + + private List calcEdgeOperations(Mapping mapping) { List edges = sourceGraph.getEdges(); List result = new ArrayList<>(); From 785ff9ed9ffa69858df27b6c56779b65803ffc2f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 07:53:45 +1000 Subject: [PATCH 146/294] cleanup --- .../java/graphql/schema/diffing/DiffImpl.java | 93 ++++++++++--------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index a0b13bd0d9..bca648b00e 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -88,7 +88,7 @@ List diffImpl(Mapping startMapping, List relevantSourceLi continue; } if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { - getSibling( + addSiblingToQueue( mappingEntry.level, queue, upperBoundCost, @@ -99,7 +99,7 @@ List diffImpl(Mapping startMapping, List relevantSourceLi mappingEntry); } if (mappingEntry.level < graphSize) { - generateChildren(mappingEntry, + addChildToQueue(mappingEntry, mappingEntry.level + 1, queue, upperBoundCost, @@ -117,37 +117,35 @@ List diffImpl(Mapping startMapping, List relevantSourceLi } - // level starts at 1 indicating the level in the search tree to look for the next mapping - private void generateChildren(MappingEntry parentEntry, - int level, - PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestFullMapping, - AtomicReference> bestEdit, - List sourceList, - List targetList + // this calculates all children for the provided parentEntry, but only the first is directly added to the queue + private void addChildToQueue(MappingEntry parentEntry, + int level, // the level of the new Mapping Entry we want to add + PriorityQueue queue, + AtomicDouble upperBound, + AtomicReference bestFullMapping, + AtomicReference> bestEdit, + List sourceList, + List targetList ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; assertTrue(level - 1 == partialMapping.size()); - // TODO: iterates over all target vertices ArrayList availableTargetVertices = new ArrayList<>(targetList); availableTargetVertices.removeAll(partialMapping.getTargets()); assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); // level starts at 1 ... therefore level - 1 is the current one we want to extend Vertex v_i = sourceList.get(level - 1); - + // the cost matrix is for the non mapped vertices int costMatrixSize = sourceList.size() - level + 1; -// double[][] costMatrix = new double[costMatrixSize][costMatrixSize]; + // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them + AtomicDoubleArray[] costMatrixForHungarianAlgo = new AtomicDoubleArray[costMatrixSize]; + Arrays.setAll(costMatrixForHungarianAlgo, (index) -> new AtomicDoubleArray(costMatrixSize)); AtomicDoubleArray[] costMatrix = new AtomicDoubleArray[costMatrixSize]; Arrays.setAll(costMatrix, (index) -> new AtomicDoubleArray(costMatrixSize)); - // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them - AtomicDoubleArray[] costMatrixCopy = new AtomicDoubleArray[costMatrixSize]; - Arrays.setAll(costMatrixCopy, (index) -> new AtomicDoubleArray(costMatrixSize)); -// + // we are skipping the first level -i indices Set partialMappingSourceSet = new LinkedHashSet<>(partialMapping.getSources()); Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); @@ -159,16 +157,16 @@ private void generateChildren(MappingEntry parentEntry, int j = 0; for (Vertex u : availableTargetVertices) { double cost = calcLowerBoundMappingCost(v, u, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); + costMatrixForHungarianAlgo[i - level + 1].set(j, cost); costMatrix[i - level + 1].set(j, cost); - costMatrixCopy[i - level + 1].set(j, cost); j++; } } - HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrix); + HungarianAlgorithm hungarianAlgorithm = new HungarianAlgorithm(costMatrixForHungarianAlgo); int[] assignments = hungarianAlgorithm.execute(); int editorialCostForMapping = editorialCostForMapping(partialMapping, completeSourceGraph, completeTargetGraph, new ArrayList<>()); - double costMatrixSum = getCostMatrixSum(costMatrixCopy, assignments); + double costMatrixSum = getCostMatrixSum(costMatrix, assignments); double lowerBoundForPartialMapping = editorialCostForMapping + costMatrixSum; @@ -182,7 +180,6 @@ private void generateChildren(MappingEntry parentEntry, } MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMapping); LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); -// newMappingEntry.siblingsReady = new AtomicBoolean(); newMappingEntry.mappingEntriesSiblings = siblings; newMappingEntry.assignments = assignments; newMappingEntry.availableTargetVertices = availableTargetVertices; @@ -203,10 +200,10 @@ private void generateChildren(MappingEntry parentEntry, System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); } - calculateChildren( + calculateRestOfChildren( availableTargetVertices, hungarianAlgorithm, - costMatrixCopy, + costMatrix, editorialCostForMapping, partialMapping, v_i, @@ -216,16 +213,18 @@ private void generateChildren(MappingEntry parentEntry, ); } - private void calculateChildren(List availableTargetVertices, - HungarianAlgorithm hungarianAlgorithm, - AtomicDoubleArray[] costMatrixCopy, - double editorialCostForMapping, - Mapping partialMapping, - Vertex v_i, - double upperBound, - int level, - LinkedBlockingQueue siblings + // generate all children mappings and save in MappingEntry.sibling + private void calculateRestOfChildren(List availableTargetVertices, + HungarianAlgorithm hungarianAlgorithm, + AtomicDoubleArray[] costMatrixCopy, + double editorialCostForMapping, + Mapping partialMapping, + Vertex v_i, + double upperBound, + int level, + LinkedBlockingQueue siblings ) { + // starting from 1 as we already generated the first one for (int child = 1; child < availableTargetVertices.size(); child++) { int[] assignments = hungarianAlgorithm.nextChild(); if (hungarianAlgorithm.costMatrix[0].get(assignments[0]) == Integer.MAX_VALUE) { @@ -247,15 +246,14 @@ private void calculateChildren(List availableTargetVertices, sibling.assignments = assignments; sibling.availableTargetVertices = availableTargetVertices; - // first child we add to the queue, otherwise save it for later -// System.out.println("add child " + child); siblings.add(sibling); } siblings.add(LAST_ELEMENT); } - private void getSibling( + // this retrieves the next sibling from MappingEntry.sibling and adds it to the queue if the lowerBound is less than the current upperBound + private void addSiblingToQueue( int level, PriorityQueue queue, AtomicDouble upperBoundCost, @@ -304,14 +302,17 @@ private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignment return costMatrixSum; } - public static int editorialCostForMapping(Mapping partialOrFullMapping, + /** + * a partial mapping introduces a sub graph. The editorial cost is only calculated with respect to this sub graph. + */ + public static int editorialCostForMapping(Mapping mapping, SchemaGraph sourceGraph, SchemaGraph targetGraph, List editOperationsResult) { int cost = 0; - for (int i = 0; i < partialOrFullMapping.size(); i++) { - Vertex sourceVertex = partialOrFullMapping.getSource(i); - Vertex targetVertex = partialOrFullMapping.getTarget(i); + for (int i = 0; i < mapping.size(); i++) { + Vertex sourceVertex = mapping.getSource(i); + Vertex targetVertex = mapping.getTarget(i); // Vertex changing (relabeling) boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); if (!equalNodes) { @@ -329,11 +330,11 @@ public static int editorialCostForMapping(Mapping partialOrFullMapping, // edge deletion or relabeling for (Edge sourceEdge : edges) { // only edges relevant to the subgraph - if (!partialOrFullMapping.containsSource(sourceEdge.getOne()) || !partialOrFullMapping.containsSource(sourceEdge.getTwo())) { + if (!mapping.containsSource(sourceEdge.getOne()) || !mapping.containsSource(sourceEdge.getTwo())) { continue; } - Vertex target1 = partialOrFullMapping.getTarget(sourceEdge.getOne()); - Vertex target2 = partialOrFullMapping.getTarget(sourceEdge.getTwo()); + Vertex target1 = mapping.getTarget(sourceEdge.getOne()); + Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); Edge targetEdge = targetGraph.getEdge(target1, target2); if (targetEdge == null) { editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); @@ -347,11 +348,11 @@ public static int editorialCostForMapping(Mapping partialOrFullMapping, //TODO: iterates over all edges in the target Graph for (Edge targetEdge : targetGraph.getEdges()) { // only subgraph edges - if (!partialOrFullMapping.containsTarget(targetEdge.getOne()) || !partialOrFullMapping.containsTarget(targetEdge.getTwo())) { + if (!mapping.containsTarget(targetEdge.getOne()) || !mapping.containsTarget(targetEdge.getTwo())) { continue; } - Vertex sourceFrom = partialOrFullMapping.getSource(targetEdge.getOne()); - Vertex sourceTo = partialOrFullMapping.getSource(targetEdge.getTwo()); + Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); + Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); cost++; From e7152973492a435a7fa18b5656c2046652107435 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 09:08:30 +1000 Subject: [PATCH 147/294] cleanup --- .../java/graphql/schema/diffing/DiffImpl.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index bca648b00e..6707e3146a 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -100,7 +100,6 @@ List diffImpl(Mapping startMapping, List relevantSourceLi } if (mappingEntry.level < graphSize) { addChildToQueue(mappingEntry, - mappingEntry.level + 1, queue, upperBoundCost, bestFullMapping, @@ -119,7 +118,6 @@ List diffImpl(Mapping startMapping, List relevantSourceLi // this calculates all children for the provided parentEntry, but only the first is directly added to the queue private void addChildToQueue(MappingEntry parentEntry, - int level, // the level of the new Mapping Entry we want to add PriorityQueue queue, AtomicDouble upperBound, AtomicReference bestFullMapping, @@ -129,16 +127,17 @@ private void addChildToQueue(MappingEntry parentEntry, ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; - assertTrue(level - 1 == partialMapping.size()); + int level = parentEntry.level;; + assertTrue(level == partialMapping.size()); ArrayList availableTargetVertices = new ArrayList<>(targetList); availableTargetVertices.removeAll(partialMapping.getTargets()); assertTrue(availableTargetVertices.size() + partialMapping.size() == targetList.size()); - // level starts at 1 ... therefore level - 1 is the current one we want to extend - Vertex v_i = sourceList.get(level - 1); + Vertex v_i = sourceList.get(level); // the cost matrix is for the non mapped vertices - int costMatrixSize = sourceList.size() - level + 1; + int costMatrixSize = sourceList.size() - level; + System.out.println("matrix size: " + costMatrixSize); // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them AtomicDoubleArray[] costMatrixForHungarianAlgo = new AtomicDoubleArray[costMatrixSize]; @@ -152,13 +151,13 @@ private void addChildToQueue(MappingEntry parentEntry, // costMatrix[0] is the row for v_i - for (int i = level - 1; i < sourceList.size(); i++) { + for (int i = level; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int j = 0; for (Vertex u : availableTargetVertices) { double cost = calcLowerBoundMappingCost(v, u, partialMapping.getSources(), partialMappingSourceSet, partialMapping.getTargets(), partialMappingTargetSet); - costMatrixForHungarianAlgo[i - level + 1].set(j, cost); - costMatrix[i - level + 1].set(j, cost); + costMatrixForHungarianAlgo[i - level].set(j, cost); + costMatrix[i - level].set(j, cost); j++; } } @@ -178,7 +177,7 @@ private void addChildToQueue(MappingEntry parentEntry, if (lowerBoundForPartialMapping >= upperBound.doubleValue()) { return; } - MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level, lowerBoundForPartialMapping); + MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level + 1, lowerBoundForPartialMapping); LinkedBlockingQueue siblings = new LinkedBlockingQueue<>(); newMappingEntry.mappingEntriesSiblings = siblings; newMappingEntry.assignments = assignments; @@ -187,7 +186,7 @@ private void addChildToQueue(MappingEntry parentEntry, queue.add(newMappingEntry); Mapping fullMapping = partialMapping.copy(); for (int i = 0; i < assignments.length; i++) { - fullMapping.add(sourceList.get(level - 1 + i), availableTargetVertices.get(assignments[i])); + fullMapping.add(sourceList.get(level + i), availableTargetVertices.get(assignments[i])); } // assertTrue(fullMapping.size() == sourceGraph.size()); @@ -197,7 +196,7 @@ private void addChildToQueue(MappingEntry parentEntry, upperBound.set(costForFullMapping); bestFullMapping.set(fullMapping); bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); + System.out.println("setting new best edit at level " + (level + 1) + " with size " + editOperations.size() + " at level " + level); } calculateRestOfChildren( @@ -208,7 +207,7 @@ private void addChildToQueue(MappingEntry parentEntry, partialMapping, v_i, upperBound.get(), - level, + level + 1, siblings ); } From fa4624bc3c7f14ad292153e41ba2f33e96267e4e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 09:28:49 +1000 Subject: [PATCH 148/294] refactoring --- .../java/graphql/schema/diffing/DiffImpl.java | 61 +----------------- .../diffing/EditorialCostForMapping.java | 64 +++++++++++++++++++ .../graphql/schema/diffing/SchemaDiffing.java | 16 +---- 3 files changed, 68 insertions(+), 73 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/EditorialCostForMapping.java diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 6707e3146a..d6a784354c 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicReference; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; public class DiffImpl { @@ -31,9 +32,8 @@ private static class MappingEntry { public int[] assignments; public List availableTargetVertices; - Mapping partialMapping = new Mapping(); - int level; + int level; // = partialMapping.size double lowerBoundCost; public MappingEntry(Mapping partialMapping, int level, double lowerBoundCost) { @@ -150,7 +150,6 @@ private void addChildToQueue(MappingEntry parentEntry, Set partialMappingTargetSet = new LinkedHashSet<>(partialMapping.getTargets()); - // costMatrix[0] is the row for v_i for (int i = level; i < sourceList.size(); i++) { Vertex v = sourceList.get(i); int j = 0; @@ -304,62 +303,6 @@ private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignment /** * a partial mapping introduces a sub graph. The editorial cost is only calculated with respect to this sub graph. */ - public static int editorialCostForMapping(Mapping mapping, - SchemaGraph sourceGraph, - SchemaGraph targetGraph, - List editOperationsResult) { - int cost = 0; - for (int i = 0; i < mapping.size(); i++) { - Vertex sourceVertex = mapping.getSource(i); - Vertex targetVertex = mapping.getTarget(i); - // Vertex changing (relabeling) - boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); - if (!equalNodes) { - if (sourceVertex.isIsolated()) { - editOperationsResult.add(EditOperation.insertVertex("Insert" + targetVertex, sourceVertex, targetVertex)); - } else if (targetVertex.isIsolated()) { - editOperationsResult.add(EditOperation.deleteVertex("Delete " + sourceVertex, sourceVertex, targetVertex)); - } else { - editOperationsResult.add(EditOperation.changeVertex("Change " + sourceVertex + " to " + targetVertex, sourceVertex, targetVertex)); - } - cost++; - } - } - List edges = sourceGraph.getEdges(); - // edge deletion or relabeling - for (Edge sourceEdge : edges) { - // only edges relevant to the subgraph - if (!mapping.containsSource(sourceEdge.getOne()) || !mapping.containsSource(sourceEdge.getTwo())) { - continue; - } - Vertex target1 = mapping.getTarget(sourceEdge.getOne()); - Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); - Edge targetEdge = targetGraph.getEdge(target1, target2); - if (targetEdge == null) { - editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); - cost++; - } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { - editOperationsResult.add(EditOperation.changeEdge("Change " + sourceEdge + " to " + targetEdge, sourceEdge, targetEdge)); - cost++; - } - } - - //TODO: iterates over all edges in the target Graph - for (Edge targetEdge : targetGraph.getEdges()) { - // only subgraph edges - if (!mapping.containsTarget(targetEdge.getOne()) || !mapping.containsTarget(targetEdge.getTwo())) { - continue; - } - Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); - Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); - if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { - editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); - cost++; - } - } - return cost; - } - // lower bound mapping cost between for v -> u in respect to a partial mapping // this is BMa diff --git a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java new file mode 100644 index 0000000000..c6c5888dfc --- /dev/null +++ b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java @@ -0,0 +1,64 @@ +package graphql.schema.diffing; + +import java.util.List; + +public class EditorialCostForMapping { + + public static int editorialCostForMapping(Mapping mapping, // can be a partial mapping + SchemaGraph sourceGraph, // the whole graph + SchemaGraph targetGraph, // the whole graph + List editOperationsResult) { + int cost = 0; + for (int i = 0; i < mapping.size(); i++) { + Vertex sourceVertex = mapping.getSource(i); + Vertex targetVertex = mapping.getTarget(i); + // Vertex changing (relabeling) + boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties()); + if (!equalNodes) { + if (sourceVertex.isIsolated()) { + editOperationsResult.add(EditOperation.insertVertex("Insert" + targetVertex, sourceVertex, targetVertex)); + } else if (targetVertex.isIsolated()) { + editOperationsResult.add(EditOperation.deleteVertex("Delete " + sourceVertex, sourceVertex, targetVertex)); + } else { + editOperationsResult.add(EditOperation.changeVertex("Change " + sourceVertex + " to " + targetVertex, sourceVertex, targetVertex)); + } + cost++; + } + } + List edges = sourceGraph.getEdges(); + // edge deletion or relabeling + for (Edge sourceEdge : edges) { + // only edges relevant to the subgraph + if (!mapping.containsSource(sourceEdge.getOne()) || !mapping.containsSource(sourceEdge.getTwo())) { + continue; + } + Vertex target1 = mapping.getTarget(sourceEdge.getOne()); + Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); + Edge targetEdge = targetGraph.getEdge(target1, target2); + if (targetEdge == null) { + editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); + cost++; + } else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) { + editOperationsResult.add(EditOperation.changeEdge("Change " + sourceEdge + " to " + targetEdge, sourceEdge, targetEdge)); + cost++; + } + } + + //TODO: iterates over all edges in the target Graph + for (Edge targetEdge : targetGraph.getEdges()) { + // only subgraph edges + if (!mapping.containsTarget(targetEdge.getOne()) || !mapping.containsTarget(targetEdge.getTwo())) { + continue; + } + Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); + Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); + if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { + editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); + cost++; + } + } + return cost; + } + + +} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index a7e2393d26..d43fd85600 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,25 +1,13 @@ package graphql.schema.diffing; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; -import com.google.common.collect.Multisets; -import com.google.common.util.concurrent.AtomicDouble; -import com.google.common.util.concurrent.AtomicDoubleArray; import graphql.schema.GraphQLSchema; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicReference; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; public class SchemaDiffing { @@ -49,7 +37,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t System.out.println("fixed mappings: " + fixedMappings.size() + " vs " + sourceGraph.size()); if (fixedMappings.size() == sourceGraph.size()) { ArrayList result = new ArrayList<>(); - DiffImpl.editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); + editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); return result; } DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); From 389eeb99c927072ca04a990847c3dc813c678632 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 10:00:35 +1000 Subject: [PATCH 149/294] refactoring --- .../java/graphql/schema/diffing/DiffImpl.java | 79 ++++++++++--------- .../diffing/EditorialCostForMapping.java | 3 + .../graphql/schema/diffing/SchemaDiffing.java | 31 -------- 3 files changed, 46 insertions(+), 67 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index d6a784354c..b554bd4079 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -14,6 +14,7 @@ import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static graphql.Assert.assertTrue; @@ -47,6 +48,12 @@ public MappingEntry() { } } + static class OptimalEdit { + List mappings = new ArrayList<>(); + List> listOfEditOperations = new ArrayList<>(); + int ged = Integer.MAX_VALUE; + } + public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { this.completeSourceGraph = completeSourceGraph; this.completeTargetGraph = completeTargetGraph; @@ -55,16 +62,14 @@ public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph List diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { - AtomicDouble upperBoundCost = new AtomicDouble(Double.MAX_VALUE); - AtomicReference bestFullMapping = new AtomicReference<>(); - AtomicReference> bestEdit = new AtomicReference<>(); + OptimalEdit optimalEdit = new OptimalEdit(); int graphSize = relevantSourceList.size(); int mappingCost = editorialCostForMapping(startMapping, completeSourceGraph, completeTargetGraph, new ArrayList<>()); int level = startMapping.size(); - MappingEntry firstMappingEntry = new MappingEntry(startMapping,level,mappingCost); - System.out.println("first entry: lower bound: " + mappingCost + " at level " + level ); + MappingEntry firstMappingEntry = new MappingEntry(startMapping, level, mappingCost); + System.out.println("first entry: lower bound: " + mappingCost + " at level " + level); PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); @@ -84,16 +89,14 @@ List diffImpl(Mapping startMapping, List relevantSourceLi // if ((++counter) % 100 == 0) { // System.out.println((counter) + " entry at level"); // } - if (mappingEntry.lowerBoundCost >= upperBoundCost.doubleValue()) { + if (mappingEntry.lowerBoundCost >= optimalEdit.ged) { continue; } if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { addSiblingToQueue( mappingEntry.level, queue, - upperBoundCost, - bestFullMapping, - bestEdit, + optimalEdit, relevantSourceList, relevantTargetList, mappingEntry); @@ -101,17 +104,15 @@ List diffImpl(Mapping startMapping, List relevantSourceLi if (mappingEntry.level < graphSize) { addChildToQueue(mappingEntry, queue, - upperBoundCost, - bestFullMapping, - bestEdit, + optimalEdit, relevantSourceList, relevantTargetList ); } } - System.out.println("ged cost: " + upperBoundCost.doubleValue()); + System.out.println("ged cost: " + optimalEdit.ged); - return bestEdit.get(); + return optimalEdit.listOfEditOperations.get(0); } @@ -119,15 +120,14 @@ List diffImpl(Mapping startMapping, List relevantSourceLi // this calculates all children for the provided parentEntry, but only the first is directly added to the queue private void addChildToQueue(MappingEntry parentEntry, PriorityQueue queue, - AtomicDouble upperBound, - AtomicReference bestFullMapping, - AtomicReference> bestEdit, + OptimalEdit optimalEdit, List sourceList, List targetList ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; - int level = parentEntry.level;; + int level = parentEntry.level; + ; assertTrue(level == partialMapping.size()); ArrayList availableTargetVertices = new ArrayList<>(targetList); @@ -173,7 +173,7 @@ private void addChildToQueue(MappingEntry parentEntry, Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMapping >= upperBound.doubleValue()) { + if (lowerBoundForPartialMapping >= optimalEdit.ged) { return; } MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level + 1, lowerBoundForPartialMapping); @@ -191,11 +191,8 @@ private void addChildToQueue(MappingEntry parentEntry, // assertTrue(fullMapping.size() == sourceGraph.size()); List editOperations = new ArrayList<>(); int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); - if (costForFullMapping < upperBound.doubleValue()) { - upperBound.set(costForFullMapping); - bestFullMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + (level + 1) + " with size " + editOperations.size() + " at level " + level); + if (costForFullMapping < optimalEdit.ged) { + updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); } calculateRestOfChildren( @@ -205,12 +202,23 @@ private void addChildToQueue(MappingEntry parentEntry, editorialCostForMapping, partialMapping, v_i, - upperBound.get(), + optimalEdit.ged, level + 1, siblings ); } + private void updateOptimalEdit(OptimalEdit optimalEdit, int newGed, Mapping mapping, List editOperations) { + if (newGed < optimalEdit.ged) { + optimalEdit.ged = newGed; + optimalEdit.listOfEditOperations.clear(); + optimalEdit.mappings.clear(); + optimalEdit.listOfEditOperations.add(editOperations); + optimalEdit.mappings.add(mapping); + System.out.println("setting new best edit at level " + (mapping.size()) + " with size " + editOperations.size()); + } + } + // generate all children mappings and save in MappingEntry.sibling private void calculateRestOfChildren(List availableTargetVertices, HungarianAlgorithm hungarianAlgorithm, @@ -218,7 +226,7 @@ private void calculateRestOfChildren(List availableTargetVertices, double editorialCostForMapping, Mapping partialMapping, Vertex v_i, - double upperBound, + int upperBound, int level, LinkedBlockingQueue siblings ) { @@ -254,9 +262,7 @@ private void calculateRestOfChildren(List availableTargetVertices, private void addSiblingToQueue( int level, PriorityQueue queue, - AtomicDouble upperBoundCost, - AtomicReference bestFullMapping, - AtomicReference> bestEdit, + OptimalEdit optimalEdit, List sourceList, List targetGraph, MappingEntry mappingEntry) throws InterruptedException { @@ -266,7 +272,7 @@ private void addSiblingToQueue( mappingEntry.siblingsFinished = true; return; } - if (sibling.lowerBoundCost < upperBoundCost.doubleValue()) { + if (sibling.lowerBoundCost < optimalEdit.ged) { // System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); @@ -279,18 +285,19 @@ private void addSiblingToQueue( // assertTrue(fullMapping.size() == this.sourceGraph.size()); List editOperations = new ArrayList<>(); int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); - if (costForFullMapping < upperBoundCost.doubleValue()) { - upperBoundCost.set(costForFullMapping); - bestFullMapping.set(fullMapping); - bestEdit.set(editOperations); - System.out.println("setting new best edit at level " + level + " with size " + editOperations.size() + " at level " + level); - + if (costForFullMapping < optimalEdit.ged) { + updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); } } else { // System.out.println("sibling not good enough"); } } + private void updateBestEdit(int cost, List editOperations) { + + + } + private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { double costMatrixSum = 0; diff --git a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java index c6c5888dfc..b2913e0b5f 100644 --- a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java +++ b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java @@ -4,6 +4,9 @@ public class EditorialCostForMapping { + /** + * a mapping introduces a subgraph consisting of all vertices and all edges between these vertices + */ public static int editorialCostForMapping(Mapping mapping, // can be a partial mapping SchemaGraph sourceGraph, // the whole graph SchemaGraph targetGraph, // the whole graph diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index d43fd85600..2d16780a61 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -60,37 +60,6 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t List editOperations = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); - - -// Mapping overallMapping = new Mapping(); -// ArrayList overallEdits = new ArrayList<>(); -// -// for (List contextId : isolatedVertices.contexts.rowKeySet()) { -// Set sourceList = isolatedVertices.contexts.row(contextId).keySet().iterator().next(); -// Set targetList = isolatedVertices.contexts.get(contextId, sourceList); -// assertTrue(sourceList.size() == targetList.size()); -// System.out.println(); -// if (sourceList.size() == 1) { -// Vertex sourceVertex = sourceList.iterator().next(); -// Vertex targetVertex = targetList.iterator().next(); -// overallMapping.add(sourceVertex, targetVertex); -// continue; -// } -// System.out.println("contextId: " + contextId + " with vertices: " + sourceList.size()); -// List editOperations = diffImpl.diffImpl(new ArrayList<>(sourceList), new ArrayList<>(targetList)); -// -// for (EditOperation editOperation : editOperations) { -// if (editOperation.getOperation() == EditOperation.Operation.CHANGE_VERTEX || -// editOperation.getOperation() == EditOperation.Operation.INSERT_VERTEX || -// editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { -// overallMapping.add(editOperation.getSourceVertex(), editOperation.getTargetVertex()); -// overallEdits.add(editOperation); -// } -// } -// } - -// List edgeOperations = calcEdgeOperations(overallMapping); -// overallEdits.addAll(edgeOperations); return editOperations; } From 3704492c3cdc7dabbbc1906b74e2823ac1a17685 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 11:17:33 +1000 Subject: [PATCH 150/294] save all edit operations --- .../java/graphql/schema/diffing/DiffImpl.java | 25 ++++++++----------- .../graphql/schema/diffing/SchemaDiffing.java | 16 ++++++++---- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index b554bd4079..bbcc48a5f6 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -48,7 +48,7 @@ public MappingEntry() { } } - static class OptimalEdit { + public static class OptimalEdit { List mappings = new ArrayList<>(); List> listOfEditOperations = new ArrayList<>(); int ged = Integer.MAX_VALUE; @@ -60,7 +60,7 @@ public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph this.isolatedVertices = isolatedVertices; } - List diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { + OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { OptimalEdit optimalEdit = new OptimalEdit(); @@ -89,7 +89,7 @@ List diffImpl(Mapping startMapping, List relevantSourceLi // if ((++counter) % 100 == 0) { // System.out.println((counter) + " entry at level"); // } - if (mappingEntry.lowerBoundCost >= optimalEdit.ged) { + if (mappingEntry.lowerBoundCost > optimalEdit.ged) { continue; } if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { @@ -112,7 +112,7 @@ List diffImpl(Mapping startMapping, List relevantSourceLi } System.out.println("ged cost: " + optimalEdit.ged); - return optimalEdit.listOfEditOperations.get(0); + return optimalEdit; } @@ -137,7 +137,6 @@ private void addChildToQueue(MappingEntry parentEntry, // the cost matrix is for the non mapped vertices int costMatrixSize = sourceList.size() - level; - System.out.println("matrix size: " + costMatrixSize); // costMatrix gets modified by the hungarian algorithm ... therefore we create two of them AtomicDoubleArray[] costMatrixForHungarianAlgo = new AtomicDoubleArray[costMatrixSize]; @@ -173,7 +172,7 @@ private void addChildToQueue(MappingEntry parentEntry, Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMapping >= optimalEdit.ged) { + if (lowerBoundForPartialMapping > optimalEdit.ged) { return; } MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level + 1, lowerBoundForPartialMapping); @@ -188,12 +187,9 @@ private void addChildToQueue(MappingEntry parentEntry, fullMapping.add(sourceList.get(level + i), availableTargetVertices.get(assignments[i])); } -// assertTrue(fullMapping.size() == sourceGraph.size()); List editOperations = new ArrayList<>(); int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); - if (costForFullMapping < optimalEdit.ged) { - updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); - } + updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); calculateRestOfChildren( availableTargetVertices, @@ -216,6 +212,9 @@ private void updateOptimalEdit(OptimalEdit optimalEdit, int newGed, Mapping mapp optimalEdit.listOfEditOperations.add(editOperations); optimalEdit.mappings.add(mapping); System.out.println("setting new best edit at level " + (mapping.size()) + " with size " + editOperations.size()); + } else if (newGed == optimalEdit.ged) { + optimalEdit.listOfEditOperations.add(editOperations); + optimalEdit.mappings.add(mapping); } } @@ -272,7 +271,7 @@ private void addSiblingToQueue( mappingEntry.siblingsFinished = true; return; } - if (sibling.lowerBoundCost < optimalEdit.ged) { + if (sibling.lowerBoundCost <= optimalEdit.ged) { // System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); @@ -285,9 +284,7 @@ private void addSiblingToQueue( // assertTrue(fullMapping.size() == this.sourceGraph.size()); List editOperations = new ArrayList<>(); int costForFullMapping = editorialCostForMapping(fullMapping, completeSourceGraph, completeTargetGraph, editOperations); - if (costForFullMapping < optimalEdit.ged) { - updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); - } + updateOptimalEdit(optimalEdit, costForFullMapping, fullMapping, editOperations); } else { // System.out.println("sibling not good enough"); } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 2d16780a61..144ca4c63d 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -18,11 +18,17 @@ public class SchemaDiffing { public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); - return diffImpl(sourceGraph, targetGraph); + return diffImpl(sourceGraph, targetGraph).get(0); } + public List> diffGraphQLSchemaAllEdits(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { + sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); + targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); + return diffImpl(sourceGraph, targetGraph); + } + - List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { + private List> diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); FillupIsolatedVertices fillupIsolatedVertices = new FillupIsolatedVertices(sourceGraph, targetGraph); @@ -38,7 +44,7 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t if (fixedMappings.size() == sourceGraph.size()) { ArrayList result = new ArrayList<>(); editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); - return result; + return Collections.singletonList(result); } DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); @@ -59,8 +65,8 @@ List diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) t targetGraphVertices.addAll(nonMappedTarget); - List editOperations = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); - return editOperations; + DiffImpl.OptimalEdit optimalEdit = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); + return optimalEdit.listOfEditOperations; } private void sortListBasedOnPossibleMapping(List sourceVertices, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { From 5643c463f88ce0e92bf40c0aa903e16650b29052 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Thu, 6 Oct 2022 14:52:51 +1100 Subject: [PATCH 151/294] Avoid allocating a type resolve env if the type is already an object type (#2980) * Avoid allocating a type resolve env if the type is already an object type * Avoid allocating a type resolve env if the type is already an object type - restored protected method * Avoid allocating a type resolve env if the type is already an object type - restored direct env allocation code --- .../graphql/execution/ExecutionStrategy.java | 25 +++++++++++-------- .../java/graphql/execution/ResolveType.java | 14 ++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 41d80eccf1..2286192afd 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -349,21 +349,21 @@ protected CompletableFuture handleFetchingException(ExecutionContext exec .build(); try { - return asyncHandleException(dataFetcherExceptionHandler, handlerParameters, executionContext); + return asyncHandleException(dataFetcherExceptionHandler, handlerParameters); } catch (Exception handlerException) { handlerParameters = DataFetcherExceptionHandlerParameters.newExceptionParameters() .dataFetchingEnvironment(environment) .exception(handlerException) .build(); - return asyncHandleException(new SimpleDataFetcherExceptionHandler(), handlerParameters, executionContext); + return asyncHandleException(new SimpleDataFetcherExceptionHandler(), handlerParameters); } } - private CompletableFuture asyncHandleException(DataFetcherExceptionHandler handler, DataFetcherExceptionHandlerParameters handlerParameters, ExecutionContext executionContext) { + private CompletableFuture asyncHandleException(DataFetcherExceptionHandler handler, DataFetcherExceptionHandlerParameters handlerParameters) { //noinspection unchecked - return handler.handleException(handlerParameters) - .thenApply(handlerResult -> (T) DataFetcherResult.newResult().errors(handlerResult.getErrors()).build() - ); + return handler.handleException(handlerParameters).thenApply( + handlerResult -> (T) DataFetcherResult.newResult().errors(handlerResult.getErrors()).build() + ); } /** @@ -620,7 +620,7 @@ protected CompletableFuture completeValueForScalar(ExecutionCon protected CompletableFuture completeValueForEnum(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLEnumType enumType, Object result) { Object serialized; try { - serialized = enumType.serialize(result); + serialized = enumType.serialize(result, executionContext.getGraphQLContext(), executionContext.getLocale()); } catch (CoercingSerializeException e) { serialized = handleCoercionProblem(executionContext, parameters, e); } @@ -633,7 +633,7 @@ protected CompletableFuture completeValueForEnum(ExecutionConte } /** - * Called to turn an java object value into an graphql object value + * Called to turn a java object value into an graphql object value * * @param executionContext contains the top level execution parameters * @param parameters contains the parameters holding the fields to be executed and source object @@ -649,7 +649,7 @@ protected CompletableFuture completeValueForObject(ExecutionCon .schema(executionContext.getGraphQLSchema()) .objectType(resolvedObjectType) .fragments(executionContext.getFragmentsByName()) - .variables(executionContext.getVariables()) + .variables(executionContext.getCoercedVariables().toMap()) .build(); MergedSelectionSet subFields = fieldCollector.collectFields(collectorParameters, parameters.getField()); @@ -679,7 +679,6 @@ private Object handleCoercionProblem(ExecutionContext context, ExecutionStrategy return null; } - /** * Converts an object that is known to should be an Iterable into one * @@ -694,6 +693,10 @@ protected Iterable toIterable(Object result) { } protected GraphQLObjectType resolveType(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLType fieldType) { + // we can avoid a method call and type resolver environment allocation if we know it's an object type + if (fieldType instanceof GraphQLObjectType) { + return (GraphQLObjectType) fieldType; + } return resolvedType.resolveType(executionContext, parameters.getField(), parameters.getSource(), parameters.getExecutionStepInfo(), fieldType, parameters.getLocalContext()); } @@ -743,7 +746,7 @@ protected GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObject } /** - * See (http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability), + * See (...), *

* If a non nullable child field type actually resolves to a null value and the parent type is nullable * then the parent must in fact become null diff --git a/src/main/java/graphql/execution/ResolveType.java b/src/main/java/graphql/execution/ResolveType.java index fbc9b5f536..3ef9c556fb 100644 --- a/src/main/java/graphql/execution/ResolveType.java +++ b/src/main/java/graphql/execution/ResolveType.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.Assert; import graphql.Internal; import graphql.TypeResolutionEnvironment; import graphql.normalized.ExecutableNormalizedField; @@ -19,9 +20,9 @@ @Internal public class ResolveType { - public GraphQLObjectType resolveType(ExecutionContext executionContext, MergedField field, Object source, ExecutionStepInfo executionStepInfo, GraphQLType fieldType, Object localContext) { - GraphQLObjectType resolvedType; + Assert.assertTrue(fieldType instanceof GraphQLInterfaceType || fieldType instanceof GraphQLUnionType, + () -> "The passed in fieldType MUST be an interface or union type : " + fieldType.getClass().getName()); DataFetchingFieldSelectionSet fieldSelectionSet = buildSelectionSet(executionContext, field, (GraphQLOutputType) fieldType, executionStepInfo); TypeResolutionEnvironment env = TypeResolutionParameters.newParameters() .field(field) @@ -35,13 +36,10 @@ public GraphQLObjectType resolveType(ExecutionContext executionContext, MergedFi .schema(executionContext.getGraphQLSchema()) .build(); if (fieldType instanceof GraphQLInterfaceType) { - resolvedType = resolveTypeForInterface(env, (GraphQLInterfaceType) fieldType); - } else if (fieldType instanceof GraphQLUnionType) { - resolvedType = resolveTypeForUnion(env, (GraphQLUnionType) fieldType); + return resolveTypeForInterface(env, (GraphQLInterfaceType) fieldType); } else { - resolvedType = (GraphQLObjectType) fieldType; + return resolveTypeForUnion(env, (GraphQLUnionType) fieldType); } - return resolvedType; } private DataFetchingFieldSelectionSet buildSelectionSet(ExecutionContext executionContext, MergedField field, GraphQLOutputType fieldType, ExecutionStepInfo executionStepInfo) { @@ -65,11 +63,9 @@ private GraphQLObjectType resolveAbstractType(TypeResolutionEnvironment env, Typ if (result == null) { throw new UnresolvedTypeException(abstractType); } - if (!env.getSchema().isPossibleType(abstractType, result)) { throw new UnresolvedTypeException(abstractType, result); } - return result; } From 066c751e01120a9b29531a698769e72fb99bcb67 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Thu, 6 Oct 2022 17:13:38 +1100 Subject: [PATCH 152/294] Avoids allocating a copy of the field names set inside the ES (#2981) --- src/main/java/graphql/execution/AsyncExecutionStrategy.java | 6 ++---- src/main/java/graphql/execution/MergedSelectionSet.java | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/execution/AsyncExecutionStrategy.java b/src/main/java/graphql/execution/AsyncExecutionStrategy.java index bdac02f5a9..025505188f 100644 --- a/src/main/java/graphql/execution/AsyncExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncExecutionStrategy.java @@ -45,9 +45,8 @@ public CompletableFuture execute(ExecutionContext executionCont ExecutionStrategyInstrumentationContext executionStrategyCtx = ExecutionStrategyInstrumentationContext.nonNullCtx(instrumentation.beginExecutionStrategy(instrumentationParameters, executionContext.getInstrumentationState())); MergedSelectionSet fields = parameters.getFields(); - Set fieldNames = fields.keySet(); + List fieldNames = fields.getKeys(); Async.CombinedBuilder futures = Async.ofExpectedSize(fields.size()); - List resolvedFields = new ArrayList<>(fieldNames.size()); for (String fieldName : fieldNames) { MergedField currentField = fields.getSubField(fieldName); @@ -55,7 +54,6 @@ public CompletableFuture execute(ExecutionContext executionCont ExecutionStrategyParameters newParameters = parameters .transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters)); - resolvedFields.add(fieldName); CompletableFuture future = resolveFieldWithInfo(executionContext, newParameters); futures.add(future); } @@ -63,7 +61,7 @@ public CompletableFuture execute(ExecutionContext executionCont executionStrategyCtx.onDispatched(overallResult); futures.await().whenComplete((completeValueInfos, throwable) -> { - BiConsumer, Throwable> handleResultsConsumer = handleResults(executionContext, resolvedFields, overallResult); + BiConsumer, Throwable> handleResultsConsumer = handleResults(executionContext, fieldNames, overallResult); if (throwable != null) { handleResultsConsumer.accept(null, throwable.getCause()); return; diff --git a/src/main/java/graphql/execution/MergedSelectionSet.java b/src/main/java/graphql/execution/MergedSelectionSet.java index 6c49b52cbd..f549cec199 100644 --- a/src/main/java/graphql/execution/MergedSelectionSet.java +++ b/src/main/java/graphql/execution/MergedSelectionSet.java @@ -1,6 +1,7 @@ package graphql.execution; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import graphql.Assert; import graphql.PublicApi; @@ -13,10 +14,10 @@ @PublicApi public class MergedSelectionSet { - private final Map subFields; + private final ImmutableMap subFields; private MergedSelectionSet(Map subFields) { - this.subFields = Assert.assertNotNull(subFields); + this.subFields = ImmutableMap.copyOf(Assert.assertNotNull(subFields)); } public Map getSubFields() { From 55a3f59f9b5382a34000d9f324efb754458d3f7c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 6 Oct 2022 20:07:25 +1000 Subject: [PATCH 153/294] improve mapping --- .../diffing/FillupIsolatedVertices.java | 201 ++++++++++++++++-- .../graphql/schema/diffing/SchemaDiffing.java | 3 + .../graphql/schema/diffing/SchemaGraph.java | 22 ++ .../schema/diffing/SchemaGraphFactory.java | 10 +- 4 files changed, 222 insertions(+), 14 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 46aa776b9d..3c7e0a4e11 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -1,6 +1,5 @@ package graphql.schema.diffing; -import com.google.common.collect.ArrayTable; import com.google.common.collect.BiMap; import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBiMap; @@ -51,7 +50,7 @@ public class FillupIsolatedVertices { static { typeContexts.put(FIELD, fieldContext()); - typeContexts.put(ARGUMENT, argumentsForFieldsContexts()); + typeContexts.put(ARGUMENT, argumentsContexts()); typeContexts.put(INPUT_FIELD, inputFieldContexts()); typeContexts.put(DUMMY_TYPE_VERTEX, dummyTypeContext()); typeContexts.put(OBJECT, objectContext()); @@ -192,7 +191,18 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return INPUT_OBJECT.equals(vertex.getType()); } }; - List contexts = Arrays.asList(inputObject); + VertexContextSegment inputObjectName = new VertexContextSegment() { + @Override + public String idForVertex(Vertex inputObject, SchemaGraph schemaGraph) { + return inputObject.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(inputObject, inputObjectName); return contexts; } @@ -388,7 +398,66 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { VertexContextSegment appliedDirectiveName = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { - return appliedDirective.getName(); + int appliedDirectiveIndex = schemaGraph.getAppliedDirectiveIndex(appliedDirective); + return appliedDirectiveIndex + ":" + appliedDirective.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() { + @Override + public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { + if ("source-5".equals(appliedDirective.getDebugName())) { + System.out.println("yo"); + } + Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + return appliedDirectiveContainer.getType() + "." + appliedDirectiveContainer.getName(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + VertexContextSegment parentOfContainer = new VertexContextSegment() { + @Override + public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { + if ("source-5".equals(appliedDirective.getDebugName())) { + System.out.println("yo"); + } + Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + switch (container.getType()) { + case FIELD: + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + case OBJECT: + return OBJECT; + case INTERFACE: + return INTERFACE; + case INPUT_FIELD: + Vertex inputObject = schemaGraph.getInputObjectForInputField(container); + return inputObject.getType() + "." + inputObject.getName(); + case ARGUMENT: + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container); + return fieldOrDirective.getType() + "." + fieldOrDirective.getName(); + case INPUT_OBJECT: + return INPUT_OBJECT; + case ENUM: + return ENUM; + case UNION: + return UNION; + case SCALAR: + return SCALAR; + case ENUM_VALUE: + Vertex enumVertex = schemaGraph.getEnumForEnumValue(container); + return enumVertex.getType() + "." + enumVertex.getName(); + default: + throw new IllegalStateException("Unexpected directive container type " + container.getType()); + } } @Override @@ -397,10 +466,50 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } }; - List contexts = Arrays.asList(appliedDirectiveType, appliedDirectiveName); + VertexContextSegment parentOfParentOfContainer = new VertexContextSegment() { + @Override + public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { + if ("source-5".equals(appliedDirective.getDebugName())) { + System.out.println("yo"); + } + Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + switch (container.getType()) { + case FIELD: + case OBJECT: + case INTERFACE: + case INPUT_FIELD: + case INPUT_OBJECT: + case ENUM: + case ENUM_VALUE: + case UNION: + case SCALAR: + return ""; + case ARGUMENT: + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container); + switch (fieldOrDirective.getType()) { + case FIELD: + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + case DIRECTIVE: + return ""; + default: + return Assert.assertShouldNeverHappen(); + } + default: + throw new IllegalStateException("Unexpected directive container type " + container.getType()); + } + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + List contexts = Arrays.asList(appliedDirectiveType, parentOfParentOfContainer, parentOfContainer, appliedDirectiveContainer, appliedDirectiveName); return contexts; } + private static List appliedArgumentContext() { VertexContextSegment appliedArgumentType = new VertexContextSegment() { @Override @@ -417,7 +526,8 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); - return appliedDirective.getName(); + int appliedDirectiveIndex = schemaGraph.getAppliedDirectiveIndex(appliedDirective); + return appliedDirectiveIndex + ":" + appliedDirective.getName(); } @Override @@ -425,6 +535,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; + VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { @@ -438,13 +549,77 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - VertexContextSegment parentOfAppliedDirectiveContainer = new VertexContextSegment() { + VertexContextSegment parentOfContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); - Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); - Vertex parent = schemaGraph.getParentSchemaElement(appliedDirectiveContainer); - return parent.getName(); + Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + switch (container.getType()) { + case FIELD: + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + case OBJECT: + return OBJECT; + case INTERFACE: + return INTERFACE; + case INPUT_FIELD: + Vertex inputObject = schemaGraph.getInputObjectForInputField(container); + return inputObject.getType() + "." + inputObject.getName(); + case ARGUMENT: + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container); + return fieldOrDirective.getType() + "." + fieldOrDirective.getName(); + case INPUT_OBJECT: + return INPUT_OBJECT; + case ENUM: + return ENUM; + case UNION: + return UNION; + case SCALAR: + return SCALAR; + case ENUM_VALUE: + Vertex enumVertex = schemaGraph.getEnumForEnumValue(container); + return enumVertex.getType() + "." + enumVertex.getName(); + default: + throw new IllegalStateException("Unexpected directive container type " + container.getType()); + } + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return true; + } + }; + + VertexContextSegment parentOfParentOfContainer = new VertexContextSegment() { + @Override + public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { + Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); + Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + switch (container.getType()) { + case FIELD: + case OBJECT: + case INTERFACE: + case INPUT_FIELD: + case INPUT_OBJECT: + case ENUM: + case ENUM_VALUE: + case UNION: + case SCALAR: + return ""; + case ARGUMENT: + Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container); + switch (fieldOrDirective.getType()) { + case FIELD: + Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective); + return fieldsContainer.getType() + "." + fieldsContainer.getName(); + case DIRECTIVE: + return ""; + default: + return Assert.assertShouldNeverHappen(); + } + default: + throw new IllegalStateException("Unexpected directive container type " + container.getType()); + } } @Override @@ -463,7 +638,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return true; } }; - List contexts = Arrays.asList(appliedArgumentType, parentOfAppliedDirectiveContainer, appliedDirectiveContainer, appliedDirective, appliedArgumentName); + List contexts = Arrays.asList(appliedArgumentType, parentOfParentOfContainer, parentOfContainer, appliedDirectiveContainer, appliedDirective, appliedArgumentName); return contexts; } @@ -507,7 +682,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return contexts; } - private static List argumentsForFieldsContexts() { + private static List argumentsContexts() { VertexContextSegment argumentType = new VertexContextSegment() { @Override @@ -664,7 +839,7 @@ public void addIsolatedTarget(Collection isolatedTarget) { allIsolatedTarget.addAll(isolatedTarget); } -// public void putSource(Object contextId, Collection isolatedSourcedVertices) { + // public void putSource(Object contextId, Collection isolatedSourcedVertices) { // contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); // allIsolatedSource.addAll(isolatedSourcedVertices); // } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 144ca4c63d..1732bacdd6 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -49,6 +49,9 @@ private List> diffImpl(SchemaGraph sourceGraph, SchemaGraph DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); nonMappedSource.removeAll(fixedMappings.getSources()); + for(Vertex vertex: nonMappedSource) { + System.out.println("non mapped: " + vertex); + } List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); nonMappedTarget.removeAll(fixedMappings.getTargets()); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index efda7f52dc..eb48ac54e6 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -135,6 +135,22 @@ public List getAdjacentVertices(Vertex from, Predicate predicate return result; } + public List getAdjacentEdges(Vertex from, Predicate predicate) { + List result = new ArrayList<>(); + for (Edge edge : edgeByVertexPair.row(from).values()) { + Vertex v; + if (edge.getOne() == from) { + v = edge.getTwo(); + } else { + v = edge.getOne(); + } + if (predicate.test(v)) { + result.add(edge); + } + } + return result; + } + public Edge getSingleAdjacentEdge(Vertex from, Predicate predicate) { for (Edge edge : edgeByVertexPair.row(from).values()) { if (predicate.test(edge)) { @@ -226,6 +242,12 @@ public Vertex getAppliedDirectiveContainerForAppliedDirective(Vertex appliedDire return adjacentVertices.get(0); } + public int getAppliedDirectiveIndex(Vertex appliedDirective) { + List adjacentEdges = this.getAdjacentEdges(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); + assertTrue(adjacentEdges.size() == 1, () -> format("No applied directive container found for %s", appliedDirective)); + return Integer.parseInt(adjacentEdges.get(0).getLabel()); + } + public Vertex getParentSchemaElement(Vertex vertex) { switch (vertex.getType()) { case OBJECT: diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 58d4275ec2..65c95ccaad 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -12,8 +12,10 @@ import graphql.util.TraverserVisitor; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import static graphql.Assert.assertNotNull; @@ -330,6 +332,7 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph schemaGraph) { + Map countByName = new LinkedHashMap<>(); for (GraphQLDirective appliedDirective : appliedDirectives) { Vertex appliedDirectiveVertex = new Vertex(SchemaGraph.APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); appliedDirectiveVertex.add("name", appliedDirective.getName()); @@ -343,7 +346,12 @@ private void cratedAppliedDirectives(Vertex from, List applied } } schemaGraph.addVertex(appliedDirectiveVertex); - schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex)); + + // repeatable directives means we can have multiple directives with the same name + // the edge label indicates the applied directive index + Integer count = countByName.getOrDefault(appliedDirective.getName(), 0); + schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex, String.valueOf(count))); + countByName.put(appliedDirective.getName(), count + 1); } } From bb3b4e58116bd041e53d874eafa240aca48914cb Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 06:45:04 +1000 Subject: [PATCH 154/294] work --- .../java/graphql/schema/diffing/DiffImpl.java | 18 +++++++++++------ .../diffing/FillupIsolatedVertices.java | 9 --------- .../graphql/schema/diffing/SchemaDiffing.java | 20 ++++++++++--------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index bbcc48a5f6..0b6dbfcb8e 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -49,9 +49,18 @@ public MappingEntry() { } public static class OptimalEdit { - List mappings = new ArrayList<>(); - List> listOfEditOperations = new ArrayList<>(); - int ged = Integer.MAX_VALUE; + public List mappings = new ArrayList<>(); + public List> listOfEditOperations = new ArrayList<>(); + public int ged = Integer.MAX_VALUE; + + public OptimalEdit() { + + } + public OptimalEdit(List mappings, List> listOfEditOperations, int ged) { + this.mappings = mappings; + this.listOfEditOperations = listOfEditOperations; + this.ged = ged; + } } public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { @@ -110,10 +119,7 @@ OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List ); } } - System.out.println("ged cost: " + optimalEdit.ged); - return optimalEdit; - } diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 3c7e0a4e11..b3c32b9134 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -411,9 +411,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { - if ("source-5".equals(appliedDirective.getDebugName())) { - System.out.println("yo"); - } Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); return appliedDirectiveContainer.getType() + "." + appliedDirectiveContainer.getName(); } @@ -426,9 +423,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { VertexContextSegment parentOfContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { - if ("source-5".equals(appliedDirective.getDebugName())) { - System.out.println("yo"); - } Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { case FIELD: @@ -469,9 +463,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { VertexContextSegment parentOfParentOfContainer = new VertexContextSegment() { @Override public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { - if ("source-5".equals(appliedDirective.getDebugName())) { - System.out.println("yo"); - } Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { case FIELD: diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 1732bacdd6..0b04b3ae94 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -8,6 +8,7 @@ import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; +import static java.util.Collections.singletonList; public class SchemaDiffing { @@ -18,17 +19,18 @@ public class SchemaDiffing { public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); - return diffImpl(sourceGraph, targetGraph).get(0); + return diffImpl(sourceGraph, targetGraph).listOfEditOperations.get(0); } - public List> diffGraphQLSchemaAllEdits(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { + + public DiffImpl.OptimalEdit diffGraphQLSchemaAllEdits(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph); } - private List> diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { + private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targetGraph) throws Exception { int sizeDiff = targetGraph.size() - sourceGraph.size(); System.out.println("graph diff: " + sizeDiff); FillupIsolatedVertices fillupIsolatedVertices = new FillupIsolatedVertices(sourceGraph, targetGraph); @@ -42,16 +44,16 @@ private List> diffImpl(SchemaGraph sourceGraph, SchemaGraph Mapping fixedMappings = isolatedVertices.mapping; System.out.println("fixed mappings: " + fixedMappings.size() + " vs " + sourceGraph.size()); if (fixedMappings.size() == sourceGraph.size()) { - ArrayList result = new ArrayList<>(); + List result = new ArrayList<>(); editorialCostForMapping(fixedMappings, sourceGraph, targetGraph, result); - return Collections.singletonList(result); + return new DiffImpl.OptimalEdit(singletonList(fixedMappings), singletonList(result), result.size()); } DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); nonMappedSource.removeAll(fixedMappings.getSources()); - for(Vertex vertex: nonMappedSource) { - System.out.println("non mapped: " + vertex); - } +// for(Vertex vertex: nonMappedSource) { +// System.out.println("non mapped: " + vertex); +// } List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); nonMappedTarget.removeAll(fixedMappings.getTargets()); @@ -69,7 +71,7 @@ private List> diffImpl(SchemaGraph sourceGraph, SchemaGraph DiffImpl.OptimalEdit optimalEdit = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); - return optimalEdit.listOfEditOperations; + return optimalEdit; } private void sortListBasedOnPossibleMapping(List sourceVertices, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { From f4d72948b1740f2586541695fecbb89757ea54e4 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 07:32:03 +1000 Subject: [PATCH 155/294] work --- .../java/graphql/schema/diffing/DiffImpl.java | 18 +++++++++++++++++- .../graphql/schema/diffing/EditOperation.java | 19 +++++++++++++++++++ .../diffing/FillupIsolatedVertices.java | 1 - .../graphql/schema/diffing/SchemaDiffing.java | 11 +++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 0b6dbfcb8e..f24400e139 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -51,11 +51,15 @@ public MappingEntry() { public static class OptimalEdit { public List mappings = new ArrayList<>(); public List> listOfEditOperations = new ArrayList<>(); + + public List> listOfSets = new ArrayList<>(); + public int ged = Integer.MAX_VALUE; public OptimalEdit() { } + public OptimalEdit(List mappings, List> listOfEditOperations, int ged) { this.mappings = mappings; this.listOfEditOperations = listOfEditOperations; @@ -213,12 +217,24 @@ private void addChildToQueue(MappingEntry parentEntry, private void updateOptimalEdit(OptimalEdit optimalEdit, int newGed, Mapping mapping, List editOperations) { if (newGed < optimalEdit.ged) { optimalEdit.ged = newGed; + optimalEdit.listOfEditOperations.clear(); - optimalEdit.mappings.clear(); optimalEdit.listOfEditOperations.add(editOperations); + + optimalEdit.listOfSets.clear(); + optimalEdit.listOfSets.add(new LinkedHashSet<>(editOperations)); + + optimalEdit.mappings.clear(); optimalEdit.mappings.add(mapping); System.out.println("setting new best edit at level " + (mapping.size()) + " with size " + editOperations.size()); } else if (newGed == optimalEdit.ged) { + Set newSet = new LinkedHashSet<>(editOperations); + for (Set set : optimalEdit.listOfSets) { + if (set.equals(newSet)) { + return; + } + } + optimalEdit.listOfSets.add(newSet); optimalEdit.listOfEditOperations.add(editOperations); optimalEdit.mappings.add(mapping); } diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index 9b4f89f1c5..9240e98f2c 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import java.util.Objects; + public class EditOperation { private EditOperation(Operation operation, @@ -72,4 +74,21 @@ public String toString() { ", description='" + description + '\'' + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EditOperation that = (EditOperation) o; + return operation == that.operation && Objects.equals(description, that.description) && Objects.equals(sourceVertex, that.sourceVertex) && Objects.equals(targetVertex, that.targetVertex) && Objects.equals(sourceEdge, that.sourceEdge) && Objects.equals(targetEdge, that.targetEdge); + } + + @Override + public int hashCode() { + return Objects.hash(operation, description, sourceVertex, targetVertex, sourceEdge, targetEdge); + } } diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index b3c32b9134..0c6ab0e4ca 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -773,7 +773,6 @@ public void ensureGraphAreSameSize() { // System.out.println("multiple for " + vertex); // } // } - System.out.println("done isolated"); // if (sourceGraph.size() < targetGraph.size()) { // isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); // } else if (sourceGraph.size() > targetGraph.size()) { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 0b04b3ae94..790f27f715 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -71,6 +71,17 @@ private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targe DiffImpl.OptimalEdit optimalEdit = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); + System.out.println("different edit counts: " + optimalEdit.listOfEditOperations.size()); + for(int i = 0; i < optimalEdit.listOfEditOperations.size(); i++) { + System.out.println("--------------"); + System.out.println("edit: " + i); + System.out.println("--------------"); + for(EditOperation editOperation: optimalEdit.listOfEditOperations.get(i)) { + System.out.println(editOperation); + } + System.out.println("--------------"); + System.out.println("--------------"); + } return optimalEdit; } From 05891a45cc84adc34d8ee51e59b65f4c7c1d8770 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 09:38:22 +1000 Subject: [PATCH 156/294] edit operation analyzer --- .../DefaultGraphEditOperationAnalyzer.java | 96 ----- .../graphql/schema/diffing/EditOperation.java | 4 +- .../graphql/schema/diffing/SchemaDiffing.java | 13 +- .../diffing/ana/EditOperationAnalyzer.java | 336 ++++++++++++++++++ .../schema/diffing/ana/SchemaChanges.java | 98 +++++ .../ana/EditOperationAnalyzerTest.groovy | 40 +++ 6 files changed, 488 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java create mode 100644 src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java create mode 100644 src/main/java/graphql/schema/diffing/ana/SchemaChanges.java create mode 100644 src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy diff --git a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java deleted file mode 100644 index 5c0157a46a..0000000000 --- a/src/main/java/graphql/schema/diffing/DefaultGraphEditOperationAnalyzer.java +++ /dev/null @@ -1,96 +0,0 @@ -//package graphql.schema.diffing; -// -//import graphql.Assert; -//import graphql.schema.GraphQLSchema; -// -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.function.Predicate; -// -//import static graphql.Assert.assertNotNull; -// -///** -// * Higher level GraphQL semantic assigned to -// */ -//public class DefaultGraphEditOperationAnalyzer { -// -// private GraphQLSchema oldSchema; -// private GraphQLSchema newSchema; -// private SchemaGraph oldSchemaGraph; -// private SchemaGraph newSchemaGraph; -// private SchemaChangedHandler schemaChangedHandler; -// -// public DefaultGraphEditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, SchemaGraph oldSchemaGraph, SchemaGraph newSchemaGraph, SchemaChangedHandler schemaChangedHandler) { -// this.oldSchema = oldSchema; -// this.newSchema = newSchema; -// this.oldSchemaGraph = oldSchemaGraph; -// this.newSchemaGraph = newSchemaGraph; -// this.schemaChangedHandler = schemaChangedHandler; -// } -// -// public void analyzeEdits(List editOperations) { -// for (EditOperation editOperation : editOperations) { -// // 5 edit operations -// if (editOperation.getOperation() == EditOperation.Operation.DELETE_VERTEX) { -// Vertex deletedVertex = editOperation.getDetails(); -// if (!deletedVertex.getType().equals("Field")) { -// continue; -// } -// String fieldName = deletedVertex.getProperty("name"); -// // find the "dummy-type" vertex for this field -// Edge edgeToDummyTypeVertex = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> edge.getTwo().getType().equals(SchemaGraph.DUMMY_TYPE_VERTEX))); -// Vertex dummyTypeVertex = edgeToDummyTypeVertex.getTwo(); -// -// Edge edgeToObjectOrInterface = assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(deletedVertex, edge -> -// edge.getOne().getType().equals("Object") || edge.getOne().getType().equals("Interface") || -// edge.getTwo().getType().equals("Object") || edge.getTwo().getType().equals("Interface") -// )); -// Edge edgeFromDummyTypeToType = Assert.assertNotNull(oldSchemaGraph.getSingleAdjacentEdge(dummyTypeVertex, edge -> edge != edgeToDummyTypeVertex)); -// -// List relatedEditOperations = searchForOperations(editOperations, Arrays.asList( -// eo -> { -// if (eo.getOperation() == EditOperation.Operation.DELETE_VERTEX) { -// return eo.getDetails() == dummyTypeVertex; -// } -// return false; -// }, -// eo -> { -// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { -// return eo.getDetails() == edgeToObjectOrInterface; -// } -// return false; -// }, -// eo -> { -// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { -// return eo.getDetails() == edgeToDummyTypeVertex; -// } -// return false; -// }, -// eo -> { -// if (eo.getOperation() == EditOperation.Operation.DELETE_EDGE) { -// return eo.getDetails() == edgeFromDummyTypeToType; -// } -// return false; -// }) -// ); -// if (relatedEditOperations.size() == 4) { -// schemaChangedHandler.fieldRemoved("Field " + edgeToObjectOrInterface.getOne().get("name") + "." + fieldName + " removed"); -// } -// } -// } -// } -// -// private List searchForOperations(List editOperations, List> predicates) { -// List result = new ArrayList<>(); -// for (EditOperation editOperation : editOperations) { -// for (Predicate predicate : predicates) { -// if (predicate.test(editOperation)) { -// result.add(editOperation); -// } -// } -// } -// return result; -// } -// -//} diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index 9240e98f2c..bae5b63979 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -50,7 +50,7 @@ public static EditOperation changeEdge(String description, Edge sourceEdge, Edge private Edge targetEdge; - enum Operation { + public enum Operation { CHANGE_VERTEX, DELETE_VERTEX, INSERT_VERTEX, CHANGE_EDGE, INSERT_EDGE, DELETE_EDGE } @@ -75,6 +75,8 @@ public String toString() { '}'; } + + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 790f27f715..aa4eca1f8b 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,6 +1,8 @@ package graphql.schema.diffing; import graphql.schema.GraphQLSchema; +import graphql.schema.diffing.ana.EditOperationAnalyzer; +import graphql.schema.diffing.ana.SchemaChanges; import java.util.ArrayList; import java.util.Collections; @@ -20,7 +22,14 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); return diffImpl(sourceGraph, targetGraph).listOfEditOperations.get(0); + } + public List diffAndAnalyze(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { + sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); + targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); + DiffImpl.OptimalEdit optimalEdit = diffImpl(sourceGraph, targetGraph); + EditOperationAnalyzer editOperationAnalyzer = new EditOperationAnalyzer(graphQLSchema1, graphQLSchema1, sourceGraph, targetGraph); + return editOperationAnalyzer.analyzeEdits(optimalEdit.listOfEditOperations.get(0)); } public DiffImpl.OptimalEdit diffGraphQLSchemaAllEdits(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { @@ -72,11 +81,11 @@ private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targe DiffImpl.OptimalEdit optimalEdit = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); System.out.println("different edit counts: " + optimalEdit.listOfEditOperations.size()); - for(int i = 0; i < optimalEdit.listOfEditOperations.size(); i++) { + for (int i = 0; i < optimalEdit.listOfEditOperations.size(); i++) { System.out.println("--------------"); System.out.println("edit: " + i); System.out.println("--------------"); - for(EditOperation editOperation: optimalEdit.listOfEditOperations.get(i)) { + for (EditOperation editOperation : optimalEdit.listOfEditOperations.get(i)) { System.out.println(editOperation); } System.out.println("--------------"); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java new file mode 100644 index 0000000000..20ea4ae9f5 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -0,0 +1,336 @@ +package graphql.schema.diffing.ana; + +import graphql.schema.GraphQLSchema; +import graphql.schema.diffing.EditOperation; +import graphql.schema.diffing.SchemaGraph; +import graphql.schema.diffing.Vertex; +import graphql.schema.diffing.ana.SchemaChanges.SchemaChange; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.ana.SchemaChanges.*; + +/** + * Higher level GraphQL semantic assigned to + */ +public class EditOperationAnalyzer { + + private GraphQLSchema oldSchema; + private GraphQLSchema newSchema; + private SchemaGraph oldSchemaGraph; + private SchemaGraph newSchemaGraph; + + private List changes = new ArrayList<>(); + + public EditOperationAnalyzer(GraphQLSchema oldSchema, + GraphQLSchema newSchema, + SchemaGraph oldSchemaGraph, + SchemaGraph newSchemaGraph + ) { + this.oldSchema = oldSchema; + this.newSchema = newSchema; + this.oldSchemaGraph = oldSchemaGraph; + this.newSchemaGraph = newSchemaGraph; + } + + public List analyzeEdits(List editOperations) { + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case INSERT_VERTEX: + insertedVertex(editOperation); + break; + case DELETE_VERTEX: + deletedVertex(editOperation); + break; + case CHANGE_VERTEX: + changeVertex(editOperation); + break; + case INSERT_EDGE: + insertedEdge(editOperation); + break; + case DELETE_EDGE: + deletedEdge(editOperation); + break; + case CHANGE_EDGE: + changedEdge(editOperation); + break; + } + } + return changes; + } + + private void insertedVertex(EditOperation editOperation) { + switch (editOperation.getTargetVertex().getType()) { + case SchemaGraph.OBJECT: + addedObject(editOperation); + break; + case SchemaGraph.INTERFACE: + addedInterface(editOperation); + break; + case SchemaGraph.UNION: + addedUnion(editOperation); + break; + case SchemaGraph.INPUT_OBJECT: + addedInputObject(editOperation); + break; + case SchemaGraph.ENUM: + addedEnum(editOperation); + break; + case SchemaGraph.SCALAR: + addedScalar(editOperation); + break; + case SchemaGraph.FIELD: + addedField(editOperation); + break; + case SchemaGraph.INPUT_FIELD: + addedInputField(editOperation); + break; + } + + } + + private void deletedVertex(EditOperation editOperation) { + switch (editOperation.getTargetVertex().getType()) { + case SchemaGraph.OBJECT: + removedObject(editOperation); + break; + case SchemaGraph.INTERFACE: + removedInterface(editOperation); + break; + case SchemaGraph.UNION: + removedUnion(editOperation); + break; + case SchemaGraph.INPUT_OBJECT: + removedInputObject(editOperation); + break; + case SchemaGraph.ENUM: + removedEnum(editOperation); + break; + case SchemaGraph.SCALAR: + removedScalar(editOperation); + break; + } + } + + private void changeVertex(EditOperation editOperation) { + switch (editOperation.getTargetVertex().getType()) { + case SchemaGraph.OBJECT: + changedObject(editOperation); + break; + case SchemaGraph.INTERFACE: + changedInterface(editOperation); + break; + case SchemaGraph.UNION: + changedUnion(editOperation); + break; + case SchemaGraph.INPUT_OBJECT: + changedInputObject(editOperation); + break; + case SchemaGraph.ENUM: + changedEnum(editOperation); + break; + case SchemaGraph.SCALAR: + changedScalar(editOperation); + break; + case SchemaGraph.FIELD: + changedField(editOperation); + break; + case SchemaGraph.INPUT_FIELD: + changedInputFIeld(editOperation); + break; + } + + } + + private void insertedEdge(EditOperation editOperation) { + + } + + private void deletedEdge(EditOperation editOperation) { + + } + + private void changedEdge(EditOperation editOperation) { + + } + + + private void addedObject(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedInterface(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedUnion(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedInputObject(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedEnum(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedScalar(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void addedField(EditOperation editOperation) { + Vertex newField = editOperation.getTargetVertex(); + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(newField); + FieldAdded objectAdded = new FieldAdded(newField.getName(), fieldsContainerForField.getName()); + changes.add(objectAdded); + } + + private void addedInputField(EditOperation editOperation) { + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void removedObject(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void removedInterface(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void removedUnion(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void removedInputObject(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void removedEnum(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void removedScalar(EditOperation editOperation) { + String objectName = editOperation.getSourceVertex().getName(); + + ObjectRemoved change = new ObjectRemoved(objectName); + changes.add(change); + } + + private void changedObject(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedInterface(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedUnion(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedEnum(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedInputObject(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedScalar(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private void changedField(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + Vertex field = editOperation.getTargetVertex(); + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); + + FieldChanged objectAdded = new FieldChanged(field.getName(), fieldsContainerForField.getName()); + changes.add(objectAdded); + } + + private void changedInputFIeld(EditOperation editOperation) { + // object changes include: adding/removing Interface, adding/removing applied directives, changing name + String objectName = editOperation.getTargetVertex().getName(); + + ObjectAdded objectAdded = new ObjectAdded(objectName); + changes.add(objectAdded); + } + + private List searchForOperations(List editOperations, List> predicates) { + List result = new ArrayList<>(); + for (EditOperation editOperation : editOperations) { + for (Predicate predicate : predicates) { + if (predicate.test(editOperation)) { + result.add(editOperation); + } + } + } + return result; + } + +} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java new file mode 100644 index 0000000000..278725890c --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java @@ -0,0 +1,98 @@ +package graphql.schema.diffing.ana; + +public class SchemaChanges { + + public interface SchemaChange { + + } + + /** + * Type means Object, Interface, Union, InputObject, Scalar, Enum + */ + + public static class ObjectAdded implements SchemaChange { + private String name; + + public ObjectAdded(String name) { + this.name = name; + } + } + + public static class FieldAdded implements SchemaChange { + + private String name; + private String fieldsContainer; + + public FieldAdded(String name, String fieldsContainer) { + this.name = name; + this.fieldsContainer = fieldsContainer; + } + } + + public static class FieldChanged implements SchemaChange { + + private final String name; + private final String fieldsContainer; + + public FieldChanged(String name, String fieldsContainer) { + this.name = name; + this.fieldsContainer = fieldsContainer; + } + + public String getName() { + return name; + } + + + public String getFieldsContainer() { + return fieldsContainer; + } + + } + + public static class ObjectChanged implements SchemaChange { + + } + + public static class ObjectRemoved implements SchemaChange { + private String name; + + public ObjectRemoved(String name) { + this.name = name; + } + + + } + + + + + public static class FieldRemoved { + + } + + public static class InputFieldChanged { + + } + + public static class InputFieldAdded { + + } + + public static class InputFieldRemoved { + + } + + public static class DirectiveChanged { + + } + + public static class DirectiveAdded { + + } + + public static class DirectiveRemoved { + + } + +} diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy new file mode 100644 index 0000000000..a5c01427db --- /dev/null +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -0,0 +1,40 @@ +package graphql.schema.diffing.ana + +import graphql.TestUtil +import graphql.schema.GraphQLSchema +import graphql.schema.diffing.SchemaDiffing +import spock.lang.Specification + +class EditOperationAnalyzerTest extends Specification { + + def "test field changed"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello2: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.size() == 1 + (changes[0] as SchemaChanges.FieldChanged).name == "hello2" + (changes[0] as SchemaChanges.FieldChanged).fieldsContainer == "Query" + } + + + List changes( + String oldSdl, + String newSdl + ) { + def oldSchema = TestUtil.schema(oldSdl) + def newSchema = TestUtil.schema(newSdl) + def changes = new SchemaDiffing().diffAndAnalyze(oldSchema, newSchema) + return changes + } +} From 60d8030cfa85a65683551e436a22c1b79734d095 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 10:06:41 +1000 Subject: [PATCH 157/294] edit operation analyzer --- .../diffing/ana/EditOperationAnalyzer.java | 17 ++++-- .../schema/diffing/ana/SchemaChanges.java | 25 ++++++++- .../ana/EditOperationAnalyzerTest.groovy | 53 +++++++++++++++++-- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 20ea4ae9f5..3711f5b0a4 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -1,10 +1,12 @@ package graphql.schema.diffing.ana; +import graphql.Scalars; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.EditOperation; import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; import graphql.schema.diffing.ana.SchemaChanges.SchemaChange; +import graphql.schema.idl.ScalarInfo; import java.util.ArrayList; import java.util.List; @@ -139,7 +141,7 @@ private void changeVertex(EditOperation editOperation) { changedField(editOperation); break; case SchemaGraph.INPUT_FIELD: - changedInputFIeld(editOperation); + changedInputField(editOperation); break; } @@ -194,10 +196,15 @@ private void addedEnum(EditOperation editOperation) { } private void addedScalar(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); + String scalarName = editOperation.getTargetVertex().getName(); + // build in scalars can appear as added when not used in the old schema, but + // we don't want to register them as new Scalars + if(ScalarInfo.isGraphqlSpecifiedScalar(scalarName)) { + return; + } - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + ScalarAdded scalarAdded = new ScalarAdded(scalarName); + changes.add(scalarAdded); } private void addedField(EditOperation editOperation) { @@ -313,7 +320,7 @@ private void changedField(EditOperation editOperation) { changes.add(objectAdded); } - private void changedInputFIeld(EditOperation editOperation) { + private void changedInputField(EditOperation editOperation) { // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java index 278725890c..0f5345774f 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java @@ -16,17 +16,38 @@ public static class ObjectAdded implements SchemaChange { public ObjectAdded(String name) { this.name = name; } + public String getName() { + return name; + } + } + public static class ScalarAdded implements SchemaChange { + private String name; + + public ScalarAdded(String name) { + this.name = name; + } + public String getName() { + return name; + } } public static class FieldAdded implements SchemaChange { - private String name; - private String fieldsContainer; + private final String name; + private final String fieldsContainer; public FieldAdded(String name, String fieldsContainer) { this.name = name; this.fieldsContainer = fieldsContainer; } + + public String getName() { + return name; + } + + public String getFieldsContainer() { + return fieldsContainer; + } } public static class FieldChanged implements SchemaChange { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a5c01427db..5aab8e8bff 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -5,6 +5,8 @@ import graphql.schema.GraphQLSchema import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification +import static graphql.schema.diffing.ana.SchemaChanges.* + class EditOperationAnalyzerTest extends Specification { def "test field changed"() { @@ -23,12 +25,57 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.size() == 1 - (changes[0] as SchemaChanges.FieldChanged).name == "hello2" - (changes[0] as SchemaChanges.FieldChanged).fieldsContainer == "Query" + (changes[0] as FieldChanged).name == "hello2" + (changes[0] as FieldChanged).fieldsContainer == "Query" + } + + def "test field added"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello: String + newOne: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.size() == 1 + (changes[0] as FieldAdded).name == "newOne" + (changes[0] as FieldAdded).fieldsContainer == "Query" + } + + def "test Object added"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello: String + foo: Foo + } + type Foo { + id: ID + } + ''' + when: + def changes = changes(oldSdl, newSdl) + def objectAdded = changes.findAll({ it instanceof ObjectAdded }) as List + then: + objectAdded.size() == 1 + objectAdded[0].name == "Foo" } - List changes( + List changes( String oldSdl, String newSdl ) { From 62474adc34c294eb1be509762e0d50405772fb4c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 11:55:38 +1000 Subject: [PATCH 158/294] edit operation analyzer --- src/main/java/graphql/Mutable.java | 3 +- .../graphql/schema/diffing/EditOperation.java | 8 ++++ .../graphql/schema/diffing/SchemaDiffing.java | 3 +- .../java/graphql/schema/diffing/Vertex.java | 4 ++ .../diffing/ana/EditOperationAnalyzer.java | 35 +++++++++++++--- .../schema/diffing/ana/ObjectChanged.java | 40 +++++++++++++++++++ .../schema/diffing/ana/SchemaChange.java | 5 +++ .../schema/diffing/ana/SchemaChanges.java | 25 +++++++----- .../ana/EditOperationAnalyzerTest.groovy | 36 ++++++++++++++++- 9 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/ana/ObjectChanged.java create mode 100644 src/main/java/graphql/schema/diffing/ana/SchemaChange.java diff --git a/src/main/java/graphql/Mutable.java b/src/main/java/graphql/Mutable.java index 9df649d052..5cc1b30e4c 100644 --- a/src/main/java/graphql/Mutable.java +++ b/src/main/java/graphql/Mutable.java @@ -4,12 +4,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; /** * This marks a type as mutable which means after constructing it can be changed. */ @Retention(RetentionPolicy.RUNTIME) -@Target(value = {TYPE}) +@Target(value = {TYPE,METHOD}) public @interface Mutable { } diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index bae5b63979..cc9150447e 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -67,6 +67,14 @@ public Vertex getTargetVertex() { return targetVertex; } + public Edge getSourceEdge() { + return sourceEdge; + } + + public Edge getTargetEdge() { + return targetEdge; + } + @Override public String toString() { return "EditOperation{" + diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index aa4eca1f8b..91ea245246 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -2,6 +2,7 @@ import graphql.schema.GraphQLSchema; import graphql.schema.diffing.ana.EditOperationAnalyzer; +import graphql.schema.diffing.ana.SchemaChange; import graphql.schema.diffing.ana.SchemaChanges; import java.util.ArrayList; @@ -24,7 +25,7 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffImpl(sourceGraph, targetGraph).listOfEditOperations.get(0); } - public List diffAndAnalyze(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { + public List diffAndAnalyze(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); DiffImpl.OptimalEdit optimalEdit = diffImpl(sourceGraph, targetGraph); diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 42d88aef55..5807137d80 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -72,6 +72,10 @@ public String getDebugName() { return debugName; } + public boolean isOfType(String type) { + return this.type.equals(type); + } + public boolean isEqualTo(Vertex other) { return other != null && Objects.equals(this.type, other.type) && diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 3711f5b0a4..baaf92351c 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -1,15 +1,16 @@ package graphql.schema.diffing.ana; -import graphql.Scalars; import graphql.schema.GraphQLSchema; +import graphql.schema.diffing.Edge; import graphql.schema.diffing.EditOperation; import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; -import graphql.schema.diffing.ana.SchemaChanges.SchemaChange; import graphql.schema.idl.ScalarInfo; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import static graphql.Assert.assertTrue; @@ -26,6 +27,7 @@ public class EditOperationAnalyzer { private SchemaGraph newSchemaGraph; private List changes = new ArrayList<>(); + private Map objectChangedMap = new LinkedHashMap<>(); public EditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, @@ -61,6 +63,7 @@ public List analyzeEdits(List editOperations) { break; } } + changes.addAll(objectChangedMap.values()); return changes; } @@ -148,7 +151,29 @@ private void changeVertex(EditOperation editOperation) { } private void insertedEdge(EditOperation editOperation) { + Edge newEdge = editOperation.getTargetEdge(); + Vertex one = newEdge.getOne(); + Vertex two = newEdge.getTwo(); + if (newEdge.getLabel().startsWith("implements ")) { + Vertex objectVertex; + Vertex interfaceVertex; + if (newEdge.getOne().isOfType(SchemaGraph.OBJECT)) { + objectVertex = newEdge.getOne(); + interfaceVertex = newEdge.getTwo(); + } else { + objectVertex = newEdge.getTwo(); + interfaceVertex = newEdge.getOne(); + } + ObjectChanged.AddedInterfaceObjectChangeDetail addedInterfaceObjectChangeDetail = new ObjectChanged.AddedInterfaceObjectChangeDetail(interfaceVertex.getName()); + getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceObjectChangeDetail); + } + } + private ObjectChanged getObjectChanged(String newName) { + if (!objectChangedMap.containsKey(newName)) { + objectChangedMap.put(newName, new ObjectChanged(newName)); + } + return objectChangedMap.get(newName); } private void deletedEdge(EditOperation editOperation) { @@ -170,8 +195,8 @@ private void addedObject(EditOperation editOperation) { private void addedInterface(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + InterfaceAdded interfacedAdded = new InterfaceAdded(objectName); + changes.add(interfacedAdded); } private void addedUnion(EditOperation editOperation) { @@ -199,7 +224,7 @@ private void addedScalar(EditOperation editOperation) { String scalarName = editOperation.getTargetVertex().getName(); // build in scalars can appear as added when not used in the old schema, but // we don't want to register them as new Scalars - if(ScalarInfo.isGraphqlSpecifiedScalar(scalarName)) { + if (ScalarInfo.isGraphqlSpecifiedScalar(scalarName)) { return; } diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java b/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java new file mode 100644 index 0000000000..3c06d95549 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java @@ -0,0 +1,40 @@ +package graphql.schema.diffing.ana; + +import java.util.ArrayList; +import java.util.List; + +public class ObjectChanged implements SchemaChange { + private final String name; + + private final List objectChangeDetails = new ArrayList<>(); + + interface ObjectChangeDetail { + + } + + public static class AddedInterfaceObjectChangeDetail implements ObjectChangeDetail { + private final String name; + + public AddedInterfaceObjectChangeDetail(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + + public ObjectChanged(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + // this will be mutated + public List getObjectChangeDetails() { + return objectChangeDetails; + } +} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java new file mode 100644 index 0000000000..e8a2916bac --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java @@ -0,0 +1,5 @@ +package graphql.schema.diffing.ana; + +public interface SchemaChange { + +} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java index 0f5345774f..6c95f0e56e 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java @@ -2,30 +2,40 @@ public class SchemaChanges { - public interface SchemaChange { - - } - /** * Type means Object, Interface, Union, InputObject, Scalar, Enum */ - public static class ObjectAdded implements SchemaChange { private String name; public ObjectAdded(String name) { this.name = name; } + public String getName() { return name; } } + + public static class InterfaceAdded implements SchemaChange { + private String name; + + public InterfaceAdded(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + public static class ScalarAdded implements SchemaChange { private String name; public ScalarAdded(String name) { this.name = name; } + public String getName() { return name; } @@ -71,9 +81,6 @@ public String getFieldsContainer() { } - public static class ObjectChanged implements SchemaChange { - - } public static class ObjectRemoved implements SchemaChange { private String name; @@ -86,8 +93,6 @@ public ObjectRemoved(String name) { } - - public static class FieldRemoved { } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5aab8e8bff..e53b17172c 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1,7 +1,6 @@ package graphql.schema.diffing.ana import graphql.TestUtil -import graphql.schema.GraphQLSchema import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification @@ -74,6 +73,41 @@ class EditOperationAnalyzerTest extends Specification { objectAdded[0].name == "Foo" } + def "test new Interface introduced"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + type Foo { + id: ID! + } + ''' + def newSdl = ''' + type Query { + foo: Foo + } + type Foo implements Node{ + id: ID! + } + interface Node { + id: ID! + } + ''' + when: + def changes = changes(oldSdl, newSdl) + def interfaceAdded = changes.findAll({ it instanceof InterfaceAdded }) as List + def objectChanged = changes.findAll({ it instanceof ObjectChanged }) as List + then: + interfaceAdded.size() == 1 + interfaceAdded[0].name == "Node" + objectChanged.size() == 1 + objectChanged[0].name == "Foo" + def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectChanged.AddedInterfaceObjectChangeDetail }) as List + addedInterfaceDetails.size() == 1 + addedInterfaceDetails[0].name == "Node" + } + List changes( String oldSdl, From fbbf52b338bfc742d6237db216776472be440e3c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 12:33:27 +1000 Subject: [PATCH 159/294] work --- src/main/java/graphql/schema/diffing/DiffImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index f24400e139..cd2861bdd6 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -102,7 +102,7 @@ OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List // if ((++counter) % 100 == 0) { // System.out.println((counter) + " entry at level"); // } - if (mappingEntry.lowerBoundCost > optimalEdit.ged) { + if (mappingEntry.lowerBoundCost >= optimalEdit.ged) { continue; } if (mappingEntry.level > 0 && !mappingEntry.siblingsFinished) { @@ -137,7 +137,7 @@ private void addChildToQueue(MappingEntry parentEntry, ) throws Exception { Mapping partialMapping = parentEntry.partialMapping; int level = parentEntry.level; - ; + assertTrue(level == partialMapping.size()); ArrayList availableTargetVertices = new ArrayList<>(targetList); @@ -293,7 +293,7 @@ private void addSiblingToQueue( mappingEntry.siblingsFinished = true; return; } - if (sibling.lowerBoundCost <= optimalEdit.ged) { + if (sibling.lowerBoundCost < optimalEdit.ged) { // System.out.println("adding new sibling entry " + getDebugMap(sibling.partialMapping) + " at level " + level + " with candidates left: " + sibling.availableTargetVertices.size() + " at lower bound: " + sibling.lowerBoundCost); queue.add(sibling); From 58681a8ce4a3c31d862210cf78a51c9d92b61ac3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 13:11:21 +1000 Subject: [PATCH 160/294] work --- .../diffing/ana/EditOperationAnalyzer.java | 38 +++++----- .../schema/diffing/ana/InterfaceChanged.java | 51 +++++++++++++ .../schema/diffing/ana/ObjectChanged.java | 15 +++- .../ana/EditOperationAnalyzerTest.groovy | 71 ++++++++++++++++++- 4 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index baaf92351c..3f65061650 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -28,6 +28,7 @@ public class EditOperationAnalyzer { private List changes = new ArrayList<>(); private Map objectChangedMap = new LinkedHashMap<>(); + private Map interfaceChangedMap = new LinkedHashMap<>(); public EditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, @@ -64,6 +65,7 @@ public List analyzeEdits(List editOperations) { } } changes.addAll(objectChangedMap.values()); + changes.addAll(interfaceChangedMap.values()); return changes; } @@ -157,15 +159,19 @@ private void insertedEdge(EditOperation editOperation) { if (newEdge.getLabel().startsWith("implements ")) { Vertex objectVertex; Vertex interfaceVertex; - if (newEdge.getOne().isOfType(SchemaGraph.OBJECT)) { + if (one.isOfType(SchemaGraph.OBJECT) && two.isOfType(SchemaGraph.INTERFACE)) { objectVertex = newEdge.getOne(); interfaceVertex = newEdge.getTwo(); - } else { + ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); + getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); + } else if (two.isOfType(SchemaGraph.INTERFACE) && one.isOfType(SchemaGraph.OBJECT)) { objectVertex = newEdge.getTwo(); interfaceVertex = newEdge.getOne(); + ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); + getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); + }else{ + // this means we need to have an interface implementing another interface } - ObjectChanged.AddedInterfaceObjectChangeDetail addedInterfaceObjectChangeDetail = new ObjectChanged.AddedInterfaceObjectChangeDetail(interfaceVertex.getName()); - getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceObjectChangeDetail); } } @@ -176,6 +182,13 @@ private ObjectChanged getObjectChanged(String newName) { return objectChangedMap.get(newName); } + private InterfaceChanged getInterfaceChanged(String newName) { + if (!interfaceChangedMap.containsKey(newName)) { + interfaceChangedMap.put(newName, new InterfaceChanged(newName)); + } + return interfaceChangedMap.get(newName); + } + private void deletedEdge(EditOperation editOperation) { } @@ -297,11 +310,9 @@ private void changedObject(EditOperation editOperation) { } private void changedInterface(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); + String interfaceName = editOperation.getTargetVertex().getName(); + InterfaceChanged interfaceChanged = getInterfaceChanged(interfaceName); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); } private void changedUnion(EditOperation editOperation) { @@ -353,16 +364,5 @@ private void changedInputField(EditOperation editOperation) { changes.add(objectAdded); } - private List searchForOperations(List editOperations, List> predicates) { - List result = new ArrayList<>(); - for (EditOperation editOperation : editOperations) { - for (Predicate predicate : predicates) { - if (predicate.test(editOperation)) { - result.add(editOperation); - } - } - } - return result; - } } diff --git a/src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java b/src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java new file mode 100644 index 0000000000..abee40277c --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java @@ -0,0 +1,51 @@ +package graphql.schema.diffing.ana; + +import java.util.ArrayList; +import java.util.List; + +public class InterfaceChanged implements SchemaChange { + private final String name; + + private final List interfaceChangeDetails = new ArrayList<>(); + + interface InterfaceChangeDetail { + + } + + public static class AddedInterfaceToInterfaceDetail implements InterfaceChangeDetail { + private final String name; + + public AddedInterfaceToInterfaceDetail(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + public static class RemovedInterfaceFromInterfaceDetail implements InterfaceChangeDetail { + private final String name; + + public RemovedInterfaceFromInterfaceDetail(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + + public InterfaceChanged(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + // this will be mutated + public List getInterfaceChangeDetails() { + return interfaceChangeDetails; + } +} diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java b/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java index 3c06d95549..9bc61206ea 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java @@ -12,10 +12,21 @@ interface ObjectChangeDetail { } - public static class AddedInterfaceObjectChangeDetail implements ObjectChangeDetail { + public static class AddedInterfaceToObjectDetail implements ObjectChangeDetail { private final String name; - public AddedInterfaceObjectChangeDetail(String name) { + public AddedInterfaceToObjectDetail(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + public static class RemovedInterfaceToObjectDetail implements ObjectChangeDetail { + private final String name; + + public RemovedInterfaceToObjectDetail(String name) { this.name = name; } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index e53b17172c..9c85210792 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -103,11 +103,80 @@ class EditOperationAnalyzerTest extends Specification { interfaceAdded[0].name == "Node" objectChanged.size() == 1 objectChanged[0].name == "Foo" - def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectChanged.AddedInterfaceObjectChangeDetail }) as List + def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectChanged.AddedInterfaceToObjectDetail }) as List addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" } + def "interfaced renamed"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + type Foo implements Node{ + id: ID! + } + interface Node { + id: ID! + } + ''' + def newSdl = ''' + type Query { + foo: Foo + } + interface Node2 { + id: ID! + } + type Foo implements Node2{ + id: ID! + } + ''' + when: + def changes = changes(oldSdl, newSdl) + def interfaceChanged = changes.findAll({ it instanceof InterfaceChanged }) as List + then: + interfaceChanged.size() == 1 + interfaceChanged[0].name == "Node2" + } + + def "interfaced renamed and another interface added to it"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + type Foo implements Node{ + id: ID! + } + interface Node { + id: ID! + } + ''' + def newSdl = ''' + type Query { + foo: Foo + } + interface NewI { + hello: String + } + interface Node2 { + id: ID! + } + type Foo implements Node2 & NewI{ + id: ID! + hello: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + def interfaceChanged = changes.findAll({ it instanceof InterfaceChanged }) as List + then: + interfaceChanged.size() == 1 + interfaceChanged.interfaceChangeDetails.size() == 1 + (interfaceChanged.interfaceChangeDetails[0] as InterfaceChanged.AddedInterfaceToInterfaceDetail).name == "NewI" + } + List changes( String oldSdl, From 1e5285d38f5c0e8dc923d377e409c7f66cc4c60c Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 18:38:24 +1000 Subject: [PATCH 161/294] change to directed graph --- .../java/graphql/schema/diffing/DiffImpl.java | 43 ++++--- .../java/graphql/schema/diffing/Edge.java | 32 ++--- .../diffing/EditorialCostForMapping.java | 14 +-- .../diffing/FillupIsolatedVertices.java | 96 +++++++-------- .../graphql/schema/diffing/GraphPrinter.java | 2 +- .../graphql/schema/diffing/SchemaDiffing.java | 34 ++---- .../graphql/schema/diffing/SchemaGraph.java | 111 ++++++++++-------- .../schema/diffing/SchemaGraphFactory.java | 20 ++-- .../diffing/ana/EditOperationAnalyzer.java | 13 +- .../schema/diffing/SchemaDiffingTest.groovy | 28 ++--- 10 files changed, 206 insertions(+), 187 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index cd2861bdd6..d4121ec98f 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -3,19 +3,17 @@ import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; -import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDoubleArray; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; @@ -98,10 +96,10 @@ OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); -// System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); -// if ((++counter) % 100 == 0) { -// System.out.println((counter) + " entry at level"); -// } + System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); + if ((++counter) % 100 == 0) { + System.out.println((counter) + " entry at level"); + } if (mappingEntry.lowerBoundCost >= optimalEdit.ged) { continue; } @@ -182,7 +180,7 @@ private void addChildToQueue(MappingEntry parentEntry, Mapping newMappingSibling = partialMapping.extendMapping(v_i, bestExtensionTargetVertexSibling); - if (lowerBoundForPartialMapping > optimalEdit.ged) { + if (lowerBoundForPartialMapping >= optimalEdit.ged) { return; } MappingEntry newMappingEntry = new MappingEntry(newMappingSibling, level + 1, lowerBoundForPartialMapping); @@ -312,11 +310,6 @@ private void addSiblingToQueue( } } - private void updateBestEdit(int cost, List editOperations) { - - - } - private double getCostMatrixSum(AtomicDoubleArray[] costMatrix, int[] assignments) { double costMatrixSum = 0; @@ -353,7 +346,7 @@ private double calcLowerBoundMappingCost(Vertex v, for (Edge edge : adjacentEdgesV) { // test if this an inner edge: meaning both edges vertices are part of the non mapped vertices // or: at least one edge is part of the partial mapping - if (!partialMappingSourceSet.contains(edge.getOne()) && !partialMappingSourceSet.contains(edge.getTwo())) { + if (!partialMappingSourceSet.contains(edge.getFrom()) && !partialMappingSourceSet.contains(edge.getTo())) { multisetLabelsV.add(edge.getLabel()); } } @@ -361,8 +354,8 @@ private double calcLowerBoundMappingCost(Vertex v, List adjacentEdgesU = completeTargetGraph.getAdjacentEdges(u); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { - // test if this is an inner edge - if (!partialMappingTargetSet.contains(edge.getOne()) && !partialMappingTargetSet.contains(edge.getTwo())) { + // test if this is an inner edge (meaning it not part of the subgraph induced by the partial mapping) + if (!partialMappingTargetSet.contains(edge.getFrom()) && !partialMappingTargetSet.contains(edge.getTo())) { multisetLabelsU.add(edge.getLabel()); } } @@ -386,7 +379,23 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - double result = (equalNodes ? 0 : 1) + multiSetEditDistance / 2.0 + anchoredVerticesCost; + // in the paper the multiSetEditDistance is divided by 2, because the edges are undirected and considered from both direction + // here we don't divide because the edges are directed and not counted twice + double result = (equalNodes ? 0 : 1) + multiSetEditDistance + anchoredVerticesCost; + return result; + } + + private List getDebugMap(Mapping mapping) { + List result = new ArrayList<>(); +// if (mapping.size() > 0) { +// result.add(mapping.getSource(mapping.size() - 1).getType() + " -> " + mapping.getTarget(mapping.size() - 1).getType()); +// } + for (Map.Entry entry : mapping.getMap().entrySet()) { +// if (!entry.getKey().getType().equals(entry.getValue().getType())) { +// result.add(entry.getKey().getType() + "->" + entry.getValue().getType()); +// } + result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); + } return result; } diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index 9c134e8d8e..14663371bc 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -3,36 +3,36 @@ import java.util.Objects; public class Edge { - private Vertex one; - private Vertex two; + private Vertex from; + private Vertex to; private String label = ""; public Edge(Vertex from, Vertex to) { - this.one = from; - this.two = to; + this.from = from; + this.to = to; } public Edge(Vertex from, Vertex to, String label) { - this.one = from; - this.two = to; + this.from = from; + this.to = to; this.label = label; } - public Vertex getOne() { - return one; + public Vertex getFrom() { + return from; } - public void setOne(Vertex one) { - this.one = one; + public void setFrom(Vertex from) { + this.from = from; } - public Vertex getTwo() { - return two; + public Vertex getTo() { + return to; } - public void setTwo(Vertex two) { - this.two = two; + public void setTo(Vertex to) { + this.to = to; } @@ -47,8 +47,8 @@ public String getLabel() { @Override public String toString() { return "Edge{" + - "one=" + one + - ", two=" + two + + "from=" + from + + ", to=" + to + ", label='" + label + '\'' + '}'; } diff --git a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java index b2913e0b5f..64cb51b9c2 100644 --- a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java +++ b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java @@ -32,12 +32,12 @@ public static int editorialCostForMapping(Mapping mapping, // can be a partial m // edge deletion or relabeling for (Edge sourceEdge : edges) { // only edges relevant to the subgraph - if (!mapping.containsSource(sourceEdge.getOne()) || !mapping.containsSource(sourceEdge.getTwo())) { + if (!mapping.containsSource(sourceEdge.getFrom()) || !mapping.containsSource(sourceEdge.getTo())) { continue; } - Vertex target1 = mapping.getTarget(sourceEdge.getOne()); - Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); - Edge targetEdge = targetGraph.getEdge(target1, target2); + Vertex targetFrom = mapping.getTarget(sourceEdge.getFrom()); + Vertex targetTo = mapping.getTarget(sourceEdge.getTo()); + Edge targetEdge = targetGraph.getEdge(targetFrom, targetTo); if (targetEdge == null) { editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); cost++; @@ -50,11 +50,11 @@ public static int editorialCostForMapping(Mapping mapping, // can be a partial m //TODO: iterates over all edges in the target Graph for (Edge targetEdge : targetGraph.getEdges()) { // only subgraph edges - if (!mapping.containsTarget(targetEdge.getOne()) || !mapping.containsTarget(targetEdge.getTwo())) { + if (!mapping.containsTarget(targetEdge.getFrom()) || !mapping.containsTarget(targetEdge.getTo())) { continue; } - Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); - Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); + Vertex sourceFrom = mapping.getSource(targetEdge.getFrom()); + Vertex sourceTo = mapping.getSource(targetEdge.getTo()); if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); cost++; diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 0c6ab0e4ca..7b0c236b70 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -52,7 +52,7 @@ public class FillupIsolatedVertices { typeContexts.put(FIELD, fieldContext()); typeContexts.put(ARGUMENT, argumentsContexts()); typeContexts.put(INPUT_FIELD, inputFieldContexts()); - typeContexts.put(DUMMY_TYPE_VERTEX, dummyTypeContext()); +// typeContexts.put(DUMMY_TYPE_VERTEX, dummyTypeContext()); typeContexts.put(OBJECT, objectContext()); typeContexts.put(INTERFACE, interfaceContext()); typeContexts.put(UNION, unionContext()); @@ -105,52 +105,52 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } - private static List dummyTypeContext() { - - VertexContextSegment dummyType = new VertexContextSegment() { - @Override - public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { - return vertex.getType(); - } - - @Override - public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return DUMMY_TYPE_VERTEX.equals(vertex.getType()); - } - }; - VertexContextSegment inputObjectOrFieldContainerContext = new VertexContextSegment() { - @Override - public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { - Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); - if (fieldOrInputField.getType().equals(FIELD)) { - Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrInputField); - return fieldsContainer.getType() + "." + fieldsContainer.getName(); - } else { - return schemaGraph.getInputObjectForInputField(fieldOrInputField).getName(); - } - } - - @Override - public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return true; - } - }; - VertexContextSegment inputFieldOrFieldName = new VertexContextSegment() { - @Override - public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { - Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); - return fieldOrInputField.getName(); - } - - @Override - public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { - return true; - } - }; - - List contexts = Arrays.asList(dummyType, inputObjectOrFieldContainerContext, inputFieldOrFieldName); - return contexts; - } +// private static List dummyTypeContext() { +// +// VertexContextSegment dummyType = new VertexContextSegment() { +// @Override +// public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { +// return vertex.getType(); +// } +// +// @Override +// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { +// return DUMMY_TYPE_VERTEX.equals(vertex.getType()); +// } +// }; +// VertexContextSegment inputObjectOrFieldContainerContext = new VertexContextSegment() { +// @Override +// public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { +// Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); +// if (fieldOrInputField.getType().equals(FIELD)) { +// Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrInputField); +// return fieldsContainer.getType() + "." + fieldsContainer.getName(); +// } else { +// return schemaGraph.getInputObjectForInputField(fieldOrInputField).getName(); +// } +// } +// +// @Override +// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { +// return true; +// } +// }; +// VertexContextSegment inputFieldOrFieldName = new VertexContextSegment() { +// @Override +// public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { +// Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); +// return fieldOrInputField.getName(); +// } +// +// @Override +// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { +// return true; +// } +// }; +// +// List contexts = Arrays.asList(dummyType, inputObjectOrFieldContainerContext, inputFieldOrFieldName); +// return contexts; +// } private static List scalarContext() { VertexContextSegment scalar = new VertexContextSegment() { @@ -744,7 +744,7 @@ public void ensureGraphAreSameSize() { calcPossibleMappings(typeContexts.get(FIELD), FIELD); calcPossibleMappings(typeContexts.get(ARGUMENT), ARGUMENT); calcPossibleMappings(typeContexts.get(INPUT_FIELD), INPUT_FIELD); - calcPossibleMappings(typeContexts.get(DUMMY_TYPE_VERTEX), DUMMY_TYPE_VERTEX); +// calcPossibleMappings(typeContexts.get(DUMMY_TYPE_VERTEX), DUMMY_TYPE_VERTEX); calcPossibleMappings(typeContexts.get(OBJECT), OBJECT); calcPossibleMappings(typeContexts.get(INTERFACE), INTERFACE); calcPossibleMappings(typeContexts.get(UNION), UNION); diff --git a/src/main/java/graphql/schema/diffing/GraphPrinter.java b/src/main/java/graphql/schema/diffing/GraphPrinter.java index b5ffc28a56..58d7baa6ba 100644 --- a/src/main/java/graphql/schema/diffing/GraphPrinter.java +++ b/src/main/java/graphql/schema/diffing/GraphPrinter.java @@ -14,7 +14,7 @@ public static String print(SchemaGraph schemaGraph) { dotfile.addNode("V" + Integer.toHexString(vertex.hashCode()), name, "blue"); } for (Edge edge : schemaGraph.getEdges()) { - dotfile.addEdge("V" + Integer.toHexString(edge.getOne().hashCode()), "V" + Integer.toHexString(edge.getTwo().hashCode()), edge.getLabel()); + dotfile.addEdge("V" + Integer.toHexString(edge.getFrom().hashCode()), "V" + Integer.toHexString(edge.getTo().hashCode()), edge.getLabel()); } return dotfile.print(); } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 91ea245246..f9fa45a3c0 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -3,11 +3,12 @@ import graphql.schema.GraphQLSchema; import graphql.schema.diffing.ana.EditOperationAnalyzer; import graphql.schema.diffing.ana.SchemaChange; -import graphql.schema.diffing.ana.SchemaChanges; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; @@ -61,9 +62,13 @@ private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targe DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); nonMappedSource.removeAll(fixedMappings.getSources()); -// for(Vertex vertex: nonMappedSource) { -// System.out.println("non mapped: " + vertex); -// } + for(Vertex vertex: nonMappedSource) { + System.out.println("non mapped: " + vertex); + } + for (List context : isolatedVertices.contexts.rowKeySet()) { + Map, Set> row = isolatedVertices.contexts.row(context); + System.out.println("context: " + context + " from " + row.keySet().iterator().next().size() + " to " + row.values().iterator().next().size()); + } List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); nonMappedTarget.removeAll(fixedMappings.getTargets()); @@ -114,8 +119,8 @@ private List calcEdgeOperations(Mapping mapping) { List result = new ArrayList<>(); // edge deletion or relabeling for (Edge sourceEdge : edges) { - Vertex target1 = mapping.getTarget(sourceEdge.getOne()); - Vertex target2 = mapping.getTarget(sourceEdge.getTwo()); + Vertex target1 = mapping.getTarget(sourceEdge.getFrom()); + Vertex target2 = mapping.getTarget(sourceEdge.getTo()); Edge targetEdge = targetGraph.getEdge(target1, target2); if (targetEdge == null) { result.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge)); @@ -127,8 +132,8 @@ private List calcEdgeOperations(Mapping mapping) { //TODO: iterates over all edges in the target Graph for (Edge targetEdge : targetGraph.getEdges()) { // only subgraph edges - Vertex sourceFrom = mapping.getSource(targetEdge.getOne()); - Vertex sourceTo = mapping.getSource(targetEdge.getTwo()); + Vertex sourceFrom = mapping.getSource(targetEdge.getFrom()); + Vertex sourceTo = mapping.getSource(targetEdge.getTo()); if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) { result.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge)); } @@ -159,19 +164,6 @@ private List calcEdgeOperations(Mapping mapping) { // } // } // -// private List getDebugMap(Mapping mapping) { -// List result = new ArrayList<>(); -//// if (mapping.size() > 0) { -//// result.add(mapping.getSource(mapping.size() - 1).getType() + " -> " + mapping.getTarget(mapping.size() - 1).getType()); -//// } -// for (Map.Entry entry : mapping.getMap().entrySet()) { -//// if (!entry.getKey().getType().equals(entry.getValue().getType())) { -//// result.add(entry.getKey().getType() + "->" + entry.getValue().getType()); -//// } -// result.add(entry.getKey().getDebugName() + "->" + entry.getValue().getDebugName()); -// } -// return result; -// } // // // minimum number of edit operations for a full mapping // diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index eb48ac54e6..72f0c43cd4 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -63,7 +62,8 @@ public class SchemaGraph { private Map typesByName = new LinkedHashMap<>(); private Map directivesByName = new LinkedHashMap<>(); - private Table edgeByVertexPair = HashBasedTable.create(); + private Table edgesByDirection = HashBasedTable.create(); + private Table edgesByInverseDirection = HashBasedTable.create(); private Multimap typeToVertices = LinkedHashMultimap.create(); public SchemaGraph() { @@ -73,7 +73,7 @@ public SchemaGraph() { public SchemaGraph(List vertices, List edges, Table edgeByVertexPair) { this.vertices = vertices; this.edges = edges; - this.edgeByVertexPair = edgeByVertexPair; + this.edgesByDirection = edgeByVertexPair; } public void addVertex(Vertex vertex) { @@ -81,19 +81,19 @@ public void addVertex(Vertex vertex) { typeToVertices.put(vertex.getType(), vertex); } - public void removeVertexAndEdges(Vertex vertex) { - vertices.remove(vertex); - typeToVertices.remove(vertex.getType(), vertex); - for (Iterator it = edges.iterator(); it.hasNext(); ) { - Edge edge = it.next(); - if (edge.getOne().equals(vertex) || edge.getTwo().equals(vertex)) { - edgeByVertexPair.remove(edge.getOne(), edge.getTwo()); - edgeByVertexPair.remove(edge.getTwo(), edge.getOne()); - it.remove(); - } - } - } - + // +// public void removeVertexAndEdges(Vertex vertex) { +// vertices.remove(vertex); +// typeToVertices.remove(vertex.getType(), vertex); +// for (Iterator it = edges.iterator(); it.hasNext(); ) { +// Edge edge = it.next(); +// if (edge.getFrom().equals(vertex) || edge.getTo().equals(vertex)) { +// edgeByVertexPair.remove(edge.getFrom(), edge.getTo()); +// it.remove(); +// } +// } +// } +// public void addVertices(Collection vertices) { for (Vertex vertex : vertices) { this.vertices.add(vertex); @@ -107,12 +107,12 @@ public Collection getVerticesByType(String type) { public void addEdge(Edge edge) { edges.add(edge); - edgeByVertexPair.put(edge.getOne(), edge.getTwo(), edge); - edgeByVertexPair.put(edge.getTwo(), edge.getOne(), edge); + edgesByDirection.put(edge.getFrom(), edge.getTo(), edge); + edgesByInverseDirection.put(edge.getTo(), edge.getFrom(), edge); } public List getAdjacentEdges(Vertex from) { - return new ArrayList<>(edgeByVertexPair.row(from).values()); + return new ArrayList<>(edgesByDirection.row(from).values()); } public List getAdjacentVertices(Vertex from) { @@ -121,13 +121,23 @@ public List getAdjacentVertices(Vertex from) { public List getAdjacentVertices(Vertex from, Predicate predicate) { List result = new ArrayList<>(); - for (Edge edge : edgeByVertexPair.row(from).values()) { - Vertex v; - if (edge.getOne() == from) { - v = edge.getTwo(); - } else { - v = edge.getOne(); + for (Edge edge : edgesByDirection.row(from).values()) { + Vertex v = edge.getTo(); + if (predicate.test(v)) { + result.add(v); } + } + return result; + } + + public List getAdjacentVerticesInverse(Vertex to) { + return getAdjacentVerticesInverse(to, x -> true); + } + + public List getAdjacentVerticesInverse(Vertex to, Predicate predicate) { + List result = new ArrayList<>(); + for (Edge edge : edgesByInverseDirection.row(to).values()) { + Vertex v = edge.getFrom(); if (predicate.test(v)) { result.add(v); } @@ -137,13 +147,22 @@ public List getAdjacentVertices(Vertex from, Predicate predicate public List getAdjacentEdges(Vertex from, Predicate predicate) { List result = new ArrayList<>(); - for (Edge edge : edgeByVertexPair.row(from).values()) { - Vertex v; - if (edge.getOne() == from) { - v = edge.getTwo(); - } else { - v = edge.getOne(); + for (Edge edge : edgesByDirection.row(from).values()) { + Vertex v = edge.getTo(); + if (predicate.test(v)) { + result.add(edge); } + } + return result; + } + + public List getAdjacentEdgesInverse(Vertex to) { + return getAdjacentEdgesInverse(to,x -> true); + } + public List getAdjacentEdgesInverse(Vertex to, Predicate predicate) { + List result = new ArrayList<>(); + for (Edge edge : edgesByInverseDirection.row(to).values()) { + Vertex v = edge.getFrom(); if (predicate.test(v)) { result.add(edge); } @@ -152,7 +171,7 @@ public List getAdjacentEdges(Vertex from, Predicate predicate) { } public Edge getSingleAdjacentEdge(Vertex from, Predicate predicate) { - for (Edge edge : edgeByVertexPair.row(from).values()) { + for (Edge edge : edgesByDirection.row(from).values()) { if (predicate.test(edge)) { return edge; } @@ -166,7 +185,7 @@ public List getEdges() { // null if the edge doesn't exist public @Nullable Edge getEdge(Vertex from, Vertex to) { - return edgeByVertexPair.get(from, to); + return edgesByDirection.get(from, to); } @@ -195,7 +214,7 @@ public Vertex getDirective(String name) { } public Optional findTargetVertex(Vertex from, Predicate vertexPredicate) { - return edgeByVertexPair.row(from).values().stream().map(Edge::getTwo).filter(vertexPredicate).findFirst(); + return edgesByDirection.row(from).values().stream().map(Edge::getTo).filter(vertexPredicate).findFirst(); } public int size() { @@ -213,37 +232,37 @@ public List addIsolatedVertices(int count, String debugPrefix) { } public Vertex getFieldOrDirectiveForArgument(Vertex argument) { - List adjacentVertices = getAdjacentVertices(argument, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(DIRECTIVE)); + List adjacentVertices = getAdjacentVerticesInverse(argument); assertTrue(adjacentVertices.size() == 1, () -> format("No field or directive found for %s", argument)); return adjacentVertices.get(0); } public Vertex getFieldsContainerForField(Vertex field) { - List adjacentVertices = getAdjacentVertices(field, vertex -> vertex.getType().equals(OBJECT) || vertex.getType().equals(INTERFACE)); + List adjacentVertices = getAdjacentVerticesInverse(field); assertTrue(adjacentVertices.size() == 1, () -> format("No fields container found for %s", field)); return adjacentVertices.get(0); } public Vertex getInputObjectForInputField(Vertex inputField) { - List adjacentVertices = this.getAdjacentVertices(inputField, vertex -> vertex.getType().equals(INPUT_OBJECT)); + List adjacentVertices = this.getAdjacentVerticesInverse(inputField); assertTrue(adjacentVertices.size() == 1, () -> format("No input object found for %s", inputField)); return adjacentVertices.get(0); } public Vertex getAppliedDirectiveForAppliedArgument(Vertex appliedArgument) { - List adjacentVertices = this.getAdjacentVertices(appliedArgument, vertex -> vertex.getType().equals(APPLIED_DIRECTIVE)); + List adjacentVertices = this.getAdjacentVerticesInverse(appliedArgument); assertTrue(adjacentVertices.size() == 1, () -> format("No applied directive found for %s", appliedArgument)); return adjacentVertices.get(0); } public Vertex getAppliedDirectiveContainerForAppliedDirective(Vertex appliedDirective) { - List adjacentVertices = this.getAdjacentVertices(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); + List adjacentVertices = this.getAdjacentVerticesInverse(appliedDirective); assertTrue(adjacentVertices.size() == 1, () -> format("No applied directive container found for %s", appliedDirective)); return adjacentVertices.get(0); } public int getAppliedDirectiveIndex(Vertex appliedDirective) { - List adjacentEdges = this.getAdjacentEdges(appliedDirective, vertex -> !vertex.getType().equals(APPLIED_ARGUMENT)); + List adjacentEdges = this.getAdjacentEdgesInverse(appliedDirective); assertTrue(adjacentEdges.size() == 1, () -> format("No applied directive container found for %s", appliedDirective)); return Integer.parseInt(adjacentEdges.get(0).getLabel()); } @@ -285,16 +304,16 @@ public Vertex getParentSchemaElement(Vertex vertex) { } public Vertex getEnumForEnumValue(Vertex enumValue) { - List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(ENUM)); + List adjacentVertices = this.getAdjacentVerticesInverse(enumValue); assertTrue(adjacentVertices.size() == 1, () -> format("No enum found for %s", enumValue)); return adjacentVertices.get(0); } - public Vertex getFieldOrInputFieldForDummyType(Vertex enumValue) { - List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(INPUT_FIELD)); - assertTrue(adjacentVertices.size() == 1, () -> format("No field or input field found for %s", enumValue)); - return adjacentVertices.get(0); - } +// public Vertex getFieldOrInputFieldForDummyType(Vertex enumValue) { +// List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(INPUT_FIELD)); +// assertTrue(adjacentVertices.size() == 1, () -> format("No field or input field found for %s", enumValue)); +// return adjacentVertices.get(0); +// } public List getAllAdjacentEdges(List fromList, Vertex to) { List result = new ArrayList<>(); diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 65c95ccaad..269db3ae73 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -136,12 +136,12 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); - dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); - schemaGraph.addVertex(dummyTypeVertex); - schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); +// Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); +// dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); +// schemaGraph.addVertex(dummyTypeVertex); +// schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); + Edge typeEdge = new Edge(inputFieldVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); schemaGraph.addEdge(typeEdge); } @@ -192,12 +192,12 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); - Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); - dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); - schemaGraph.addVertex(dummyTypeVertex); - schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); +// Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); +// dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); +// schemaGraph.addVertex(dummyTypeVertex); +// schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); - Edge typeEdge = new Edge(dummyTypeVertex, typeVertex); + Edge typeEdge = new Edge(fieldVertex, typeVertex); typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); schemaGraph.addEdge(typeEdge); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 3f65061650..46c35bbea7 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -11,7 +11,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Predicate; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.ana.SchemaChanges.*; @@ -154,19 +153,19 @@ private void changeVertex(EditOperation editOperation) { private void insertedEdge(EditOperation editOperation) { Edge newEdge = editOperation.getTargetEdge(); - Vertex one = newEdge.getOne(); - Vertex two = newEdge.getTwo(); + Vertex one = newEdge.getFrom(); + Vertex two = newEdge.getTo(); if (newEdge.getLabel().startsWith("implements ")) { Vertex objectVertex; Vertex interfaceVertex; if (one.isOfType(SchemaGraph.OBJECT) && two.isOfType(SchemaGraph.INTERFACE)) { - objectVertex = newEdge.getOne(); - interfaceVertex = newEdge.getTwo(); + objectVertex = newEdge.getFrom(); + interfaceVertex = newEdge.getTo(); ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); } else if (two.isOfType(SchemaGraph.INTERFACE) && one.isOfType(SchemaGraph.OBJECT)) { - objectVertex = newEdge.getTwo(); - interfaceVertex = newEdge.getOne(); + objectVertex = newEdge.getTo(); + interfaceVertex = newEdge.getFrom(); ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); }else{ diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 740cf58b3d..e7448e7c3c 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -27,7 +27,7 @@ class SchemaDiffingTest extends Specification { def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: - schemaGraph.size() == 132 + schemaGraph.size() == 91 } @@ -119,7 +119,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) diff.each { println it } then: - diff.size() == 6 + diff.size() == 4 } @@ -155,7 +155,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) diff.each { println it } then: - diff.size() == 7 + diff.size() == 5 } @@ -193,7 +193,7 @@ class SchemaDiffingTest extends Specification { * 3-8: Three new Fields + DummyTypes * 9-17: Edges from Object to new Fields (3) + Edges from Field to Dummy Type (3) + Edges from DummyType to String * */ - diff.size() == 17 + diff.size() == 11 } @@ -350,7 +350,7 @@ class SchemaDiffingTest extends Specification { /** * If we would allow to map Object to Interface this would have a result of 8 */ - diff.size() == 10 + diff.size() == 8 } @@ -390,7 +390,7 @@ class SchemaDiffingTest extends Specification { } then: - diff.size() == 19 + diff.size() == 15 } @@ -435,7 +435,7 @@ class SchemaDiffingTest extends Specification { diff.each { println it } then: // deleting 171 fields + dummyTypes + 3 edges for each field,dummyType pair = 5*171 - diff.size() == 5 * 171 + diff.size() == 3 * 171 } def "change object type name used twice"() { @@ -542,7 +542,7 @@ class SchemaDiffingTest extends Specification { } then: - diff.size() == 5 + diff.size() == 3 } @@ -893,12 +893,12 @@ class SchemaDiffingTest extends Specification { then: /** * 1. Change Cat to Bird - * 2,3,4: Insert Fish, Insert Fish.name, Insert __DummyTypeVertice - * 5. Insert Edge from Fish to Fish.name - * 6.7. Insert Edge from Fish.name -> __DummyType --> String - * 8. Insert edge from Pet -> Fish + * 2,3: Insert Fish, Insert Fish.name + * 4. Insert Edge from Fish to Fish.name + * 5 Insert Edge from Fish.name -> String + * 6. Insert edge from Pet -> Fish */ - diff.size() == 8 + diff.size() == 6 } @@ -981,7 +981,7 @@ class SchemaDiffingTest extends Specification { * It would be less operations with f2 renamed to g3, but this would defy expectations. * */ - operations.size() == 11 + operations.size() == 7 } def "arguments in fields"() { From fadc49634453c83b3344309c909c41e8ee7fecea Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 19:20:50 +1000 Subject: [PATCH 162/294] wip --- src/main/java/graphql/schema/diffing/DiffImpl.java | 8 +++----- src/main/java/graphql/schema/diffing/SchemaGraph.java | 5 +++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index d4121ec98f..d028ebd70d 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -340,7 +340,7 @@ private double calcLowerBoundMappingCost(Vertex v, // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges - List adjacentEdgesV = completeSourceGraph.getAdjacentEdges(v); + List adjacentEdgesV = completeSourceGraph.getAdjacentEdgesAndInverse(v); Multiset multisetLabelsV = HashMultiset.create(); for (Edge edge : adjacentEdgesV) { @@ -351,7 +351,7 @@ private double calcLowerBoundMappingCost(Vertex v, } } - List adjacentEdgesU = completeTargetGraph.getAdjacentEdges(u); + List adjacentEdgesU = completeTargetGraph.getAdjacentEdgesAndInverse(u); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { // test if this is an inner edge (meaning it not part of the subgraph induced by the partial mapping) @@ -379,9 +379,7 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - // in the paper the multiSetEditDistance is divided by 2, because the edges are undirected and considered from both direction - // here we don't divide because the edges are directed and not counted twice - double result = (equalNodes ? 0 : 1) + multiSetEditDistance + anchoredVerticesCost; + double result = (equalNodes ? 0 : 1) + multiSetEditDistance/2.0 + anchoredVerticesCost; return result; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 72f0c43cd4..68689c2372 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -114,6 +114,11 @@ public void addEdge(Edge edge) { public List getAdjacentEdges(Vertex from) { return new ArrayList<>(edgesByDirection.row(from).values()); } + public List getAdjacentEdgesAndInverse(Vertex fromAndTo) { + List result = new ArrayList<>(edgesByDirection.row(fromAndTo).values()); + result.addAll(edgesByInverseDirection.row(fromAndTo).values()); + return result; + } public List getAdjacentVertices(Vertex from) { return getAdjacentVertices(from, x -> true); From 2fb6d87414eb35e3cc67bcc9ac89f3b0f8ce922f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 19:40:27 +1000 Subject: [PATCH 163/294] changed calc lower bound --- .../java/graphql/schema/diffing/DiffImpl.java | 18 +++++++--- .../graphql/schema/diffing/SchemaDiffing.java | 36 +++++++++---------- .../graphql/schema/diffing/SchemaGraph.java | 8 ++++- .../schema/diffing/SchemaDiffingTest.groovy | 2 +- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index d028ebd70d..edd07bf84c 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -96,10 +96,10 @@ OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); - System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); - if ((++counter) % 100 == 0) { - System.out.println((counter) + " entry at level"); - } +// System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); +// if ((++counter) % 100 == 0) { +// System.out.println((counter) + " entry at level"); +// } if (mappingEntry.lowerBoundCost >= optimalEdit.ged) { continue; } @@ -367,6 +367,7 @@ private double calcLowerBoundMappingCost(Vertex v, for (int i = 0; i < partialMappingSourceList.size(); i++) { Vertex vPrime = partialMappingSourceList.get(i); Vertex mappedVPrime = partialMappingTargetList.get(i); + Edge sourceEdge = completeSourceGraph.getEdge(v, vPrime); String labelSourceEdge = sourceEdge != null ? sourceEdge.getLabel() : null; Edge targetEdge = completeTargetGraph.getEdge(u, mappedVPrime); @@ -374,6 +375,15 @@ private double calcLowerBoundMappingCost(Vertex v, if (!Objects.equals(labelSourceEdge, labelTargetEdge)) { anchoredVerticesCost++; } + + Edge sourceEdgeInverse = completeSourceGraph.getEdge(vPrime,v); + String labelSourceEdgeInverse = sourceEdgeInverse != null ? sourceEdgeInverse.getLabel() : null; + Edge targetEdgeInverse = completeTargetGraph.getEdge(mappedVPrime,u); + String labelTargetEdgeInverse = targetEdgeInverse != null ? targetEdgeInverse.getLabel() : null; + if (!Objects.equals(labelSourceEdgeInverse, labelTargetEdgeInverse)) { + anchoredVerticesCost++; + } + } Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index f9fa45a3c0..2ef2efebd1 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -62,13 +62,13 @@ private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targe DiffImpl diffImpl = new DiffImpl(sourceGraph, targetGraph, isolatedVertices); List nonMappedSource = new ArrayList<>(sourceGraph.getVertices()); nonMappedSource.removeAll(fixedMappings.getSources()); - for(Vertex vertex: nonMappedSource) { - System.out.println("non mapped: " + vertex); - } - for (List context : isolatedVertices.contexts.rowKeySet()) { - Map, Set> row = isolatedVertices.contexts.row(context); - System.out.println("context: " + context + " from " + row.keySet().iterator().next().size() + " to " + row.values().iterator().next().size()); - } +// for(Vertex vertex: nonMappedSource) { +// System.out.println("non mapped: " + vertex); +// } +// for (List context : isolatedVertices.contexts.rowKeySet()) { +// Map, Set> row = isolatedVertices.contexts.row(context); +// System.out.println("context: " + context + " from " + row.keySet().iterator().next().size() + " to " + row.values().iterator().next().size()); +// } List nonMappedTarget = new ArrayList<>(targetGraph.getVertices()); nonMappedTarget.removeAll(fixedMappings.getTargets()); @@ -86,17 +86,17 @@ private DiffImpl.OptimalEdit diffImpl(SchemaGraph sourceGraph, SchemaGraph targe DiffImpl.OptimalEdit optimalEdit = diffImpl.diffImpl(fixedMappings, sourceVertices, targetGraphVertices); - System.out.println("different edit counts: " + optimalEdit.listOfEditOperations.size()); - for (int i = 0; i < optimalEdit.listOfEditOperations.size(); i++) { - System.out.println("--------------"); - System.out.println("edit: " + i); - System.out.println("--------------"); - for (EditOperation editOperation : optimalEdit.listOfEditOperations.get(i)) { - System.out.println(editOperation); - } - System.out.println("--------------"); - System.out.println("--------------"); - } +// System.out.println("different edit counts: " + optimalEdit.listOfEditOperations.size()); +// for (int i = 0; i < optimalEdit.listOfEditOperations.size(); i++) { +// System.out.println("--------------"); +// System.out.println("edit: " + i); +// System.out.println("--------------"); +// for (EditOperation editOperation : optimalEdit.listOfEditOperations.get(i)) { +// System.out.println(editOperation); +// } +// System.out.println("--------------"); +// System.out.println("--------------"); +// } return optimalEdit; } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 68689c2372..819ecd9309 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -114,6 +114,7 @@ public void addEdge(Edge edge) { public List getAdjacentEdges(Vertex from) { return new ArrayList<>(edgesByDirection.row(from).values()); } + public List getAdjacentEdgesAndInverse(Vertex fromAndTo) { List result = new ArrayList<>(edgesByDirection.row(fromAndTo).values()); result.addAll(edgesByInverseDirection.row(fromAndTo).values()); @@ -162,8 +163,9 @@ public List getAdjacentEdges(Vertex from, Predicate predicate) { } public List getAdjacentEdgesInverse(Vertex to) { - return getAdjacentEdgesInverse(to,x -> true); + return getAdjacentEdgesInverse(to, x -> true); } + public List getAdjacentEdgesInverse(Vertex to, Predicate predicate) { List result = new ArrayList<>(); for (Edge edge : edgesByInverseDirection.row(to).values()) { @@ -193,6 +195,10 @@ public List getEdges() { return edgesByDirection.get(from, to); } + public @Nullable Edge getEdgeOrInverse(Vertex from, Vertex to) { + Edge result = edgesByDirection.get(from, to); + return result != null ? result : edgesByInverseDirection.get(from, to); + } public List getVertices() { return vertices; diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index e7448e7c3c..dedf0e6cdc 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -803,7 +803,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - diff.size() == 5 + diff.size() == 3 } From 8741b660fffdb1dff150a0a7777cda9f95349433 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 7 Oct 2022 20:39:44 +1000 Subject: [PATCH 164/294] changed calc lower bound --- src/main/java/graphql/schema/diffing/DiffImpl.java | 6 +++--- .../groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index edd07bf84c..5c202f35ba 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -340,7 +340,7 @@ private double calcLowerBoundMappingCost(Vertex v, // inner edge labels of u (resp. v) in regards to the partial mapping: all labels of edges // which are adjacent of u (resp. v) which are inner edges - List adjacentEdgesV = completeSourceGraph.getAdjacentEdgesAndInverse(v); + List adjacentEdgesV = completeSourceGraph.getAdjacentEdges(v); Multiset multisetLabelsV = HashMultiset.create(); for (Edge edge : adjacentEdgesV) { @@ -351,7 +351,7 @@ private double calcLowerBoundMappingCost(Vertex v, } } - List adjacentEdgesU = completeTargetGraph.getAdjacentEdgesAndInverse(u); + List adjacentEdgesU = completeTargetGraph.getAdjacentEdges(u); Multiset multisetLabelsU = HashMultiset.create(); for (Edge edge : adjacentEdgesU) { // test if this is an inner edge (meaning it not part of the subgraph induced by the partial mapping) @@ -389,7 +389,7 @@ private double calcLowerBoundMappingCost(Vertex v, Multiset intersection = Multisets.intersection(multisetLabelsV, multisetLabelsU); int multiSetEditDistance = Math.max(multisetLabelsV.size(), multisetLabelsU.size()) - intersection.size(); - double result = (equalNodes ? 0 : 1) + multiSetEditDistance/2.0 + anchoredVerticesCost; + double result = (equalNodes ? 0 : 1) + multiSetEditDistance + anchoredVerticesCost; return result; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index dedf0e6cdc..83f2c864ee 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1185,7 +1185,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 33 + operations.size() == 31 } def "built in directives"() { From 9be24eee676846a4001b47aeca31802e9bd2d716 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 9 Oct 2022 07:16:12 +1000 Subject: [PATCH 165/294] tests green --- .../schema/diffing/SchemaDiffingTest.groovy | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 83f2c864ee..134ada5d36 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -597,7 +597,7 @@ class SchemaDiffingTest extends Specification { diff.each { println it } then: - diff.size() == 59 + diff.size() == 41 } @@ -643,7 +643,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 18 + operations.size() == 12 } def "adding a few things plus introducing new interface"() { @@ -688,7 +688,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 22 + operations.size() == 16 } def "adding a few things plus introducing new interface plus changing return type"() { @@ -733,7 +733,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 24 + operations.size() == 18 } def "adding a few things plus introducing new interface plus changing return type plus adding field in Interface"() { @@ -782,7 +782,7 @@ class SchemaDiffingTest extends Specification { def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - operations.size() == 42 + operations.size() == 28 } def "add a field"() { @@ -826,9 +826,10 @@ class SchemaDiffingTest extends Specification { when: def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + diff.each { println it } then: - diff.size() == 11 + diff.size() == 7 } @@ -852,7 +853,7 @@ class SchemaDiffingTest extends Specification { def diff = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) then: - diff.size() == 9 + diff.size() == 7 } @@ -1042,7 +1043,7 @@ class SchemaDiffingTest extends Specification { operations.each { println it } then: - operations.size() == 18 + operations.size() == 14 } def "adding enum value"() { From 7268b1388c0260a52d8d529deb39727fac85f914 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 9 Oct 2022 08:17:53 +1000 Subject: [PATCH 166/294] edit operation analyzer wip --- .../graphql/schema/diffing/SchemaDiffing.java | 3 +- .../ana/EditOperationAnalysisResult.java | 50 +++ .../diffing/ana/EditOperationAnalyzer.java | 386 ++++++++++-------- ...aceChanged.java => InterfaceModified.java} | 4 +- ...ObjectChanged.java => ObjectModified.java} | 4 +- .../schema/diffing/ana/SchemaChange.java | 18 + .../schema/diffing/ana/SchemaChanges.java | 87 +++- .../ana/EditOperationAnalyzerTest.groovy | 105 +++-- 8 files changed, 403 insertions(+), 254 deletions(-) create mode 100644 src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java rename src/main/java/graphql/schema/diffing/ana/{InterfaceChanged.java => InterfaceModified.java} (90%) rename src/main/java/graphql/schema/diffing/ana/{ObjectChanged.java => ObjectModified.java} (90%) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 2ef2efebd1..6433e3bbb1 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,6 +1,7 @@ package graphql.schema.diffing; import graphql.schema.GraphQLSchema; +import graphql.schema.diffing.ana.EditOperationAnalysisResult; import graphql.schema.diffing.ana.EditOperationAnalyzer; import graphql.schema.diffing.ana.SchemaChange; @@ -26,7 +27,7 @@ public List diffGraphQLSchema(GraphQLSchema graphQLSchema1, Graph return diffImpl(sourceGraph, targetGraph).listOfEditOperations.get(0); } - public List diffAndAnalyze(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { + public EditOperationAnalysisResult diffAndAnalyze(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { sourceGraph = new SchemaGraphFactory("source-").createGraph(graphQLSchema1); targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); DiffImpl.OptimalEdit optimalEdit = diffImpl(sourceGraph, targetGraph); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java new file mode 100644 index 0000000000..7a68baf367 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -0,0 +1,50 @@ +package graphql.schema.diffing.ana; + +import java.util.Map; + +public class EditOperationAnalysisResult { + private final Map objectChanges; + private final Map interfaceChanges; + private final Map unionChanges; + private final Map enumChanges; + private final Map inputObjectChanges; + private final Map scalarChanges; + + public EditOperationAnalysisResult(Map objectChanges, + Map interfaceChanges, + Map unionChanges, + Map enumChanges, + Map inputObjectChanges, + Map scalarChanges) { + this.objectChanges = objectChanges; + this.interfaceChanges = interfaceChanges; + this.unionChanges = unionChanges; + this.enumChanges = enumChanges; + this.inputObjectChanges = inputObjectChanges; + this.scalarChanges = scalarChanges; + } + + public Map getObjectChanges() { + return objectChanges; + } + + public Map getInterfaceChanges() { + return interfaceChanges; + } + + public Map getUnionChanges() { + return unionChanges; + } + + public Map getEnumChanges() { + return enumChanges; + } + + public Map getInputObjectChanges() { + return inputObjectChanges; + } + + public Map getScalarChanges() { + return scalarChanges; + } +} diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 46c35bbea7..b4ad43e9ce 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -25,9 +25,14 @@ public class EditOperationAnalyzer { private SchemaGraph oldSchemaGraph; private SchemaGraph newSchemaGraph; - private List changes = new ArrayList<>(); - private Map objectChangedMap = new LinkedHashMap<>(); - private Map interfaceChangedMap = new LinkedHashMap<>(); +// private List changes = new ArrayList<>(); + + private Map objectChanges = new LinkedHashMap<>(); + private Map interfaceChanges = new LinkedHashMap<>(); + private Map unionChanges = new LinkedHashMap<>(); + private Map enumChanges = new LinkedHashMap<>(); + private Map inputObjectChanges = new LinkedHashMap<>(); + private Map scalarChanges = new LinkedHashMap<>(); public EditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, @@ -40,35 +45,51 @@ public EditOperationAnalyzer(GraphQLSchema oldSchema, this.newSchemaGraph = newSchemaGraph; } - public List analyzeEdits(List editOperations) { + public EditOperationAnalysisResult analyzeEdits(List editOperations) { + handleTypeVertexChanges(editOperations); + handleEdgeChanges(editOperations); +// for (EditOperation editOperation : editOperations) { +// switch (editOperation.getOperation()) { +// case INSERT_VERTEX: +// insertedVertex(editOperation); +// break; +// case DELETE_VERTEX: +// deletedVertex(editOperation); +// break; +// case CHANGE_VERTEX: +// changeVertex(editOperation); +// break; +// case INSERT_EDGE: +// insertedEdge(editOperation); +// break; +// case DELETE_EDGE: +// deletedEdge(editOperation); +// break; +// case CHANGE_EDGE: +// changedEdge(editOperation); +// break; +// } +// } + return new EditOperationAnalysisResult(objectChanges, interfaceChanges, unionChanges, enumChanges, inputObjectChanges, scalarChanges); + } + + private void handleTypeVertexChanges(List editOperations) { for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { case INSERT_VERTEX: - insertedVertex(editOperation); + insertedTypeVertex(editOperation); break; case DELETE_VERTEX: - deletedVertex(editOperation); + deletedTypeVertex(editOperation); break; case CHANGE_VERTEX: - changeVertex(editOperation); - break; - case INSERT_EDGE: - insertedEdge(editOperation); - break; - case DELETE_EDGE: - deletedEdge(editOperation); - break; - case CHANGE_EDGE: - changedEdge(editOperation); + modifiedTypeVertex(editOperation); break; } } - changes.addAll(objectChangedMap.values()); - changes.addAll(interfaceChangedMap.values()); - return changes; } - private void insertedVertex(EditOperation editOperation) { + private void insertedTypeVertex(EditOperation editOperation) { switch (editOperation.getTargetVertex().getType()) { case SchemaGraph.OBJECT: addedObject(editOperation); @@ -88,17 +109,11 @@ private void insertedVertex(EditOperation editOperation) { case SchemaGraph.SCALAR: addedScalar(editOperation); break; - case SchemaGraph.FIELD: - addedField(editOperation); - break; - case SchemaGraph.INPUT_FIELD: - addedInputField(editOperation); - break; } } - private void deletedVertex(EditOperation editOperation) { + private void deletedTypeVertex(EditOperation editOperation) { switch (editOperation.getTargetVertex().getType()) { case SchemaGraph.OBJECT: removedObject(editOperation); @@ -121,73 +136,91 @@ private void deletedVertex(EditOperation editOperation) { } } - private void changeVertex(EditOperation editOperation) { + private void modifiedTypeVertex(EditOperation editOperation) { switch (editOperation.getTargetVertex().getType()) { case SchemaGraph.OBJECT: - changedObject(editOperation); + modifiedObject(editOperation); break; case SchemaGraph.INTERFACE: - changedInterface(editOperation); - break; - case SchemaGraph.UNION: - changedUnion(editOperation); - break; - case SchemaGraph.INPUT_OBJECT: - changedInputObject(editOperation); - break; - case SchemaGraph.ENUM: - changedEnum(editOperation); - break; - case SchemaGraph.SCALAR: - changedScalar(editOperation); - break; - case SchemaGraph.FIELD: - changedField(editOperation); - break; - case SchemaGraph.INPUT_FIELD: - changedInputField(editOperation); + modifiedInterface(editOperation); break; +// case SchemaGraph.UNION: +// changedUnion(editOperation); +// break; +// case SchemaGraph.INPUT_OBJECT: +// changedInputObject(editOperation); +// break; +// case SchemaGraph.ENUM: +// changedEnum(editOperation); +// break; +// case SchemaGraph.SCALAR: +// changedScalar(editOperation); +// break; } } - private void insertedEdge(EditOperation editOperation) { - Edge newEdge = editOperation.getTargetEdge(); - Vertex one = newEdge.getFrom(); - Vertex two = newEdge.getTo(); - if (newEdge.getLabel().startsWith("implements ")) { - Vertex objectVertex; - Vertex interfaceVertex; - if (one.isOfType(SchemaGraph.OBJECT) && two.isOfType(SchemaGraph.INTERFACE)) { - objectVertex = newEdge.getFrom(); - interfaceVertex = newEdge.getTo(); - ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); - getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); - } else if (two.isOfType(SchemaGraph.INTERFACE) && one.isOfType(SchemaGraph.OBJECT)) { - objectVertex = newEdge.getTo(); - interfaceVertex = newEdge.getFrom(); - ObjectChanged.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectChanged.AddedInterfaceToObjectDetail(interfaceVertex.getName()); - getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); - }else{ - // this means we need to have an interface implementing another interface + private void handleEdgeChanges(List editOperations) { + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case INSERT_EDGE: + insertedEdge(editOperation); + break; +// case DELETE_EDGE: +// deletedEdge(editOperation); +// break; +// case CHANGE_EDGE: +// changedEdge(editOperation); +// break; } } } - private ObjectChanged getObjectChanged(String newName) { - if (!objectChangedMap.containsKey(newName)) { - objectChangedMap.put(newName, new ObjectChanged(newName)); + private void insertedEdge(EditOperation editOperation) { + Edge newEdge = editOperation.getTargetEdge(); + if (newEdge.getLabel().startsWith("implements ")) { + newInterfaceAddedToInterfaceOrObject(newEdge); } - return objectChangedMap.get(newName); } - private InterfaceChanged getInterfaceChanged(String newName) { - if (!interfaceChangedMap.containsKey(newName)) { - interfaceChangedMap.put(newName, new InterfaceChanged(newName)); + private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { + Vertex objectVertex; + Vertex interfaceVertex; + Vertex from = newEdge.getFrom(); + Vertex to = newEdge.getTo(); + if (from.isOfType(SchemaGraph.OBJECT)){ + objectVertex = newEdge.getFrom(); + interfaceVertex = newEdge.getTo(); + ObjectModified.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModified.AddedInterfaceToObjectDetail(interfaceVertex.getName()); + + // It could be a completely new Object or a modified one + + +// } else if (two.isOfType(SchemaGraph.INTERFACE) && one.isOfType(SchemaGraph.OBJECT)) { +// objectVertex = newEdge.getTo(); +// interfaceVertex = newEdge.getFrom(); +// ObjectModification.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModification.AddedInterfaceToObjectDetail(interfaceVertex.getName()); +// getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); +// } else { + // this means we need to have an interface implementing another interface } - return interfaceChangedMap.get(newName); + } +// private SchemaChange.ObjectChange getObjectChanged(String newName) { +// if (!objectChangedMap.containsKey(newName)) { +// objectChangedMap.put(newName, new ObjectModification(newName)); +// } +// return objectChangedMap.get(newName); +// } +//// +// private InterfaceChanged getInterfaceChanged(String newName) { +// if (!interfaceChangedMap.containsKey(newName)) { +// interfaceChangedMap.put(newName, new InterfaceChanged(newName)); +// } +// return interfaceChangedMap.get(newName); +// } + private void deletedEdge(EditOperation editOperation) { } @@ -199,37 +232,36 @@ private void changedEdge(EditOperation editOperation) { private void addedObject(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + objectChanges.put(objectName, objectAdded); } private void addedInterface(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); InterfaceAdded interfacedAdded = new InterfaceAdded(objectName); - changes.add(interfacedAdded); + interfaceChanges.put(objectName, interfacedAdded); } private void addedUnion(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); + String unionName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + UnionAdded unionAdded = new UnionAdded(unionName); + unionChanges.put(unionName, unionAdded); } private void addedInputObject(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); + String inputObjectName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + InputObjectAdded added = new InputObjectAdded(inputObjectName); + inputObjectChanges.put(inputObjectName, added); } private void addedEnum(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); + String enumName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); + EnumAdded enumAdded = new EnumAdded(enumName); + enumChanges.put(enumName, enumAdded); } private void addedScalar(EditOperation editOperation) { @@ -241,127 +273,127 @@ private void addedScalar(EditOperation editOperation) { } ScalarAdded scalarAdded = new ScalarAdded(scalarName); - changes.add(scalarAdded); - } - - private void addedField(EditOperation editOperation) { - Vertex newField = editOperation.getTargetVertex(); - Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(newField); - FieldAdded objectAdded = new FieldAdded(newField.getName(), fieldsContainerForField.getName()); - changes.add(objectAdded); - } - - private void addedInputField(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } + scalarChanges.put(scalarName, scalarAdded); + } + +// private void addedField(EditOperation editOperation) { +// Vertex newField = editOperation.getTargetVertex(); +// Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(newField); +// FieldAdded objectAdded = new FieldAdded(newField.getName(), fieldsContainerForField.getName()); +// changes.add(objectAdded); +// } +// +// private void addedInputField(EditOperation editOperation) { +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + objectChanges.put(objectName, change); } private void removedInterface(EditOperation editOperation) { - String objectName = editOperation.getSourceVertex().getName(); + String interfaceName = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + InterfaceRemoved change = new InterfaceRemoved(interfaceName); + interfaceChanges.put(interfaceName, change); } private void removedUnion(EditOperation editOperation) { - String objectName = editOperation.getSourceVertex().getName(); + String unionName = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + UnionRemoved change = new UnionRemoved(unionName); + unionChanges.put(unionName, change); } private void removedInputObject(EditOperation editOperation) { - String objectName = editOperation.getSourceVertex().getName(); + String name = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + InputObjectRemoved change = new InputObjectRemoved(name); + inputObjectChanges.put(name, change); } private void removedEnum(EditOperation editOperation) { - String objectName = editOperation.getSourceVertex().getName(); + String enumName = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + EnumRemoved change = new EnumRemoved(enumName); + enumChanges.put(enumName, change); } private void removedScalar(EditOperation editOperation) { - String objectName = editOperation.getSourceVertex().getName(); + String scalarName = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - changes.add(change); + ScalarRemoved change = new ScalarRemoved(scalarName); + scalarChanges.put(scalarName, change); } - private void changedObject(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name + private void modifiedObject(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); +// + ObjectModified objectModified = new ObjectModified(objectName); + objectChanges.put(objectName, objectModified); } - private void changedInterface(EditOperation editOperation) { + private void modifiedInterface(EditOperation editOperation) { String interfaceName = editOperation.getTargetVertex().getName(); - InterfaceChanged interfaceChanged = getInterfaceChanged(interfaceName); - - } - - private void changedUnion(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } - - private void changedEnum(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } - - private void changedInputObject(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } - - private void changedScalar(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } - - private void changedField(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - Vertex field = editOperation.getTargetVertex(); - Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); - - FieldChanged objectAdded = new FieldChanged(field.getName(), fieldsContainerForField.getName()); - changes.add(objectAdded); - } - - private void changedInputField(EditOperation editOperation) { - // object changes include: adding/removing Interface, adding/removing applied directives, changing name - String objectName = editOperation.getTargetVertex().getName(); - - ObjectAdded objectAdded = new ObjectAdded(objectName); - changes.add(objectAdded); - } + InterfaceModified interfaceModified = new InterfaceModified(interfaceName); + interfaceChanges.put(interfaceName, interfaceModified); + } +// +// private void changedUnion(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } +// +// private void changedEnum(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } +// +// private void changedInputObject(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } +// +// private void changedScalar(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } +// +// private void changedField(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// Vertex field = editOperation.getTargetVertex(); +// Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); +// +// FieldModified objectAdded = new FieldModified(field.getName(), fieldsContainerForField.getName()); +// changes.add(objectAdded); +// } +// +// private void changedInputField(EditOperation editOperation) { +// // object changes include: adding/removing Interface, adding/removing applied directives, changing name +// String objectName = editOperation.getTargetVertex().getName(); +// +// ObjectAdded objectAdded = new ObjectAdded(objectName); +// changes.add(objectAdded); +// } } diff --git a/src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java b/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java similarity index 90% rename from src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java rename to src/main/java/graphql/schema/diffing/ana/InterfaceModified.java index abee40277c..963c6327b7 100644 --- a/src/main/java/graphql/schema/diffing/ana/InterfaceChanged.java +++ b/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; -public class InterfaceChanged implements SchemaChange { +public class InterfaceModified implements SchemaChange.InterfaceChange { private final String name; private final List interfaceChangeDetails = new ArrayList<>(); @@ -36,7 +36,7 @@ public String getName() { } - public InterfaceChanged(String name) { + public InterfaceModified(String name) { this.name = name; } diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java similarity index 90% rename from src/main/java/graphql/schema/diffing/ana/ObjectChanged.java rename to src/main/java/graphql/schema/diffing/ana/ObjectModified.java index 9bc61206ea..05f0ae8e41 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectChanged.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; -public class ObjectChanged implements SchemaChange { +public class ObjectModified implements SchemaChange.ObjectChange { private final String name; private final List objectChangeDetails = new ArrayList<>(); @@ -36,7 +36,7 @@ public String getName() { } - public ObjectChanged(String name) { + public ObjectModified(String name) { this.name = name; } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java index e8a2916bac..c93374d520 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java @@ -1,5 +1,23 @@ package graphql.schema.diffing.ana; public interface SchemaChange { + interface ObjectChange extends SchemaChange { + + } + interface InterfaceChange extends SchemaChange { + + } + interface UnionChange extends SchemaChange { + + } + interface EnumChange extends SchemaChange { + + } + interface InputObjectChange extends SchemaChange { + + } + interface ScalarChange extends SchemaChange { + + } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java index 6c95f0e56e..b36cf4064e 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java @@ -5,7 +5,7 @@ public class SchemaChanges { /** * Type means Object, Interface, Union, InputObject, Scalar, Enum */ - public static class ObjectAdded implements SchemaChange { + public static class ObjectAdded implements SchemaChange.ObjectChange { private String name; public ObjectAdded(String name) { @@ -17,7 +17,7 @@ public String getName() { } } - public static class InterfaceAdded implements SchemaChange { + public static class InterfaceAdded implements SchemaChange.InterfaceChange { private String name; public InterfaceAdded(String name) { @@ -29,7 +29,7 @@ public String getName() { } } - public static class ScalarAdded implements SchemaChange { + public static class ScalarAdded implements SchemaChange.ScalarChange { private String name; public ScalarAdded(String name) { @@ -41,6 +41,42 @@ public String getName() { } } + public static class UnionAdded implements SchemaChange.UnionChange { + private String name; + + public UnionAdded(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public static class InputObjectAdded implements SchemaChange.InputObjectChange { + private String name; + + public InputObjectAdded(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public static class EnumAdded implements SchemaChange.EnumChange { + private String name; + + public EnumAdded(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + public static class FieldAdded implements SchemaChange { private final String name; @@ -60,12 +96,12 @@ public String getFieldsContainer() { } } - public static class FieldChanged implements SchemaChange { + public static class FieldModified implements SchemaChange { private final String name; private final String fieldsContainer; - public FieldChanged(String name, String fieldsContainer) { + public FieldModified(String name, String fieldsContainer) { this.name = name; this.fieldsContainer = fieldsContainer; } @@ -82,43 +118,58 @@ public String getFieldsContainer() { } - public static class ObjectRemoved implements SchemaChange { + public static class ObjectRemoved implements SchemaChange.ObjectChange { private String name; public ObjectRemoved(String name) { this.name = name; } - } + public static class InterfaceRemoved implements SchemaChange.InterfaceChange { + private String name; - public static class FieldRemoved { + public InterfaceRemoved(String name) { + this.name = name; + } } - public static class InputFieldChanged { - - } + public static class UnionRemoved implements SchemaChange.UnionChange { + private String name; - public static class InputFieldAdded { + public UnionRemoved(String name) { + this.name = name; + } } - public static class InputFieldRemoved { - - } + public static class ScalarRemoved implements SchemaChange.ScalarChange { + private String name; - public static class DirectiveChanged { + public ScalarRemoved(String name) { + this.name = name; + } } - public static class DirectiveAdded { + public static class InputObjectRemoved implements SchemaChange.InputObjectChange { + private String name; + + public InputObjectRemoved(String name) { + this.name = name; + } } + public static class EnumRemoved implements SchemaChange.EnumChange { + private String name; - public static class DirectiveRemoved { + public EnumRemoved(String name) { + this.name = name; + } } + } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 9c85210792..96caff123f 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -8,46 +8,46 @@ import static graphql.schema.diffing.ana.SchemaChanges.* class EditOperationAnalyzerTest extends Specification { - def "test field changed"() { - given: - def oldSdl = ''' - type Query { - hello: String - } - ''' - def newSdl = ''' - type Query { - hello2: String - } - ''' - when: - def changes = changes(oldSdl, newSdl) - then: - changes.size() == 1 - (changes[0] as FieldChanged).name == "hello2" - (changes[0] as FieldChanged).fieldsContainer == "Query" - } - - def "test field added"() { - given: - def oldSdl = ''' - type Query { - hello: String - } - ''' - def newSdl = ''' - type Query { - hello: String - newOne: String - } - ''' - when: - def changes = changes(oldSdl, newSdl) - then: - changes.size() == 1 - (changes[0] as FieldAdded).name == "newOne" - (changes[0] as FieldAdded).fieldsContainer == "Query" - } +// def "test field changed"() { +// given: +// def oldSdl = ''' +// type Query { +// hello: String +// } +// ''' +// def newSdl = ''' +// type Query { +// hello2: String +// } +// ''' +// when: +// def changes = changes(oldSdl, newSdl) +// then: +// changes +// (changes[0] as FieldModified).name == "hello2" +// (changes[0] as FieldModified).fieldsContainer == "Query" +// } +// +// def "test field added"() { +// given: +// def oldSdl = ''' +// type Query { +// hello: String +// } +// ''' +// def newSdl = ''' +// type Query { +// hello: String +// newOne: String +// } +// ''' +// when: +// def changes = changes(oldSdl, newSdl) +// then: +// changes.size() == 1 +// (changes[0] as FieldAdded).name == "newOne" +// (changes[0] as FieldAdded).fieldsContainer == "Query" +// } def "test Object added"() { given: @@ -67,10 +67,9 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def objectAdded = changes.findAll({ it instanceof ObjectAdded }) as List then: - objectAdded.size() == 1 - objectAdded[0].name == "Foo" + changes.objectChanges.size() == 1 + changes.objectChanges["Foo"]instanceof ObjectAdded } def "test new Interface introduced"() { @@ -96,14 +95,12 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def interfaceAdded = changes.findAll({ it instanceof InterfaceAdded }) as List - def objectChanged = changes.findAll({ it instanceof ObjectChanged }) as List then: - interfaceAdded.size() == 1 - interfaceAdded[0].name == "Node" - objectChanged.size() == 1 - objectChanged[0].name == "Foo" - def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectChanged.AddedInterfaceToObjectDetail }) as List + changes.interfaceChanges.size() == 1 + changes.interfaceChanges["Node"] instanceof InterfaceAdded + changes.objectChanges.size() == 1 + changes.objectChanges["Foo"] instanceof ObjectModified + def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" } @@ -134,7 +131,7 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def interfaceChanged = changes.findAll({ it instanceof InterfaceChanged }) as List + def interfaceChanged = changes.findAll({ it instanceof InterfaceModified }) as List then: interfaceChanged.size() == 1 interfaceChanged[0].name == "Node2" @@ -170,15 +167,15 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def interfaceChanged = changes.findAll({ it instanceof InterfaceChanged }) as List + def interfaceChanged = changes.findAll({ it instanceof InterfaceModified }) as List then: interfaceChanged.size() == 1 interfaceChanged.interfaceChangeDetails.size() == 1 - (interfaceChanged.interfaceChangeDetails[0] as InterfaceChanged.AddedInterfaceToInterfaceDetail).name == "NewI" + (interfaceChanged.interfaceChangeDetails[0] as InterfaceModified.AddedInterfaceToInterfaceDetail).name == "NewI" } - List changes( + EditOperationAnalysisResult changes( String oldSdl, String newSdl ) { From a87c1dc650a8b54f3e9f0ad0975838de8c016ac7 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 9 Oct 2022 09:05:22 +1000 Subject: [PATCH 167/294] edit operation analyzer wip --- .../diffing/ana/EditOperationAnalyzer.java | 63 ++++++++++++------- .../ana/EditOperationAnalyzerTest.groovy | 51 ++++++++++++--- 2 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b4ad43e9ce..ee5ad8ca06 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -1,5 +1,6 @@ package graphql.schema.diffing.ana; +import graphql.Assert; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.Edge; import graphql.schema.diffing.EditOperation; @@ -181,38 +182,57 @@ private void insertedEdge(EditOperation editOperation) { if (newEdge.getLabel().startsWith("implements ")) { newInterfaceAddedToInterfaceOrObject(newEdge); } +// else if(newEdge) } private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { - Vertex objectVertex; - Vertex interfaceVertex; Vertex from = newEdge.getFrom(); - Vertex to = newEdge.getTo(); - if (from.isOfType(SchemaGraph.OBJECT)){ - objectVertex = newEdge.getFrom(); - interfaceVertex = newEdge.getTo(); + if (from.isOfType(SchemaGraph.OBJECT)) { + if (isNewObject(from.getName())) { + return; + } + Vertex objectVertex = newEdge.getFrom(); + Vertex interfaceVertex = newEdge.getTo(); ObjectModified.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModified.AddedInterfaceToObjectDetail(interfaceVertex.getName()); + getObjectModified(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); - // It could be a completely new Object or a modified one + } else if (from.isOfType(SchemaGraph.INTERFACE)) { + if (isNewInterface(from.getName())) { + return; + } + Vertex interfaceFromVertex = newEdge.getFrom(); + Vertex interfaceVertex = newEdge.getTo(); + InterfaceModified.AddedInterfaceToInterfaceDetail addedInterfaceToObjectDetail = new InterfaceModified.AddedInterfaceToInterfaceDetail(interfaceVertex.getName()); + getInterfaceModified(interfaceFromVertex.getName()).getInterfaceChangeDetails().add(addedInterfaceToObjectDetail); + } else { + Assert.assertShouldNeverHappen("expected an implementation edge"); + } + } -// } else if (two.isOfType(SchemaGraph.INTERFACE) && one.isOfType(SchemaGraph.OBJECT)) { -// objectVertex = newEdge.getTo(); -// interfaceVertex = newEdge.getFrom(); -// ObjectModification.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModification.AddedInterfaceToObjectDetail(interfaceVertex.getName()); -// getObjectChanged(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); -// } else { - // this means we need to have an interface implementing another interface - } + private boolean isNewObject(String name) { + return objectChanges.containsKey(name) && objectChanges.get(name) instanceof ObjectAdded; + } + private boolean isNewInterface(String name) { + return interfaceChanges.containsKey(name) && interfaceChanges.get(name) instanceof InterfaceAdded; } -// private SchemaChange.ObjectChange getObjectChanged(String newName) { -// if (!objectChangedMap.containsKey(newName)) { -// objectChangedMap.put(newName, new ObjectModification(newName)); -// } -// return objectChangedMap.get(newName); -// } + private ObjectModified getObjectModified(String newName) { + if (!objectChanges.containsKey(newName)) { + objectChanges.put(newName, new ObjectModified(newName)); + } + assertTrue(objectChanges.get(newName) instanceof ObjectModified); + return (ObjectModified) objectChanges.get(newName); + } + + private InterfaceModified getInterfaceModified(String newName) { + if (!interfaceChanges.containsKey(newName)) { + interfaceChanges.put(newName, new InterfaceModified(newName)); + } + assertTrue(interfaceChanges.get(newName) instanceof InterfaceModified); + return (InterfaceModified) interfaceChanges.get(newName); + } //// // private InterfaceChanged getInterfaceChanged(String newName) { // if (!interfaceChangedMap.containsKey(newName)) { @@ -343,6 +363,7 @@ private void modifiedObject(EditOperation editOperation) { private void modifiedInterface(EditOperation editOperation) { String interfaceName = editOperation.getTargetVertex().getName(); InterfaceModified interfaceModified = new InterfaceModified(interfaceName); + // we store the modification against the new name interfaceChanges.put(interfaceName, interfaceModified); } // diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 96caff123f..a8cda76ebc 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -72,7 +72,7 @@ class EditOperationAnalyzerTest extends Specification { changes.objectChanges["Foo"]instanceof ObjectAdded } - def "test new Interface introduced"() { + def "new Interface introduced"() { given: def oldSdl = ''' type Query { @@ -100,11 +100,39 @@ class EditOperationAnalyzerTest extends Specification { changes.interfaceChanges["Node"] instanceof InterfaceAdded changes.objectChanges.size() == 1 changes.objectChanges["Foo"] instanceof ObjectModified - def addedInterfaceDetails = objectChanged[0].objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + def objectModified = changes.objectChanges["Foo"] as ObjectModified + def addedInterfaceDetails = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" } + def "Object and Interface added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo: Foo + } + type Foo implements Node{ + id: ID! + } + interface Node { + id: ID! + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.interfaceChanges.size() == 1 + changes.interfaceChanges["Node"] instanceof InterfaceAdded + changes.objectChanges.size() == 1 + changes.objectChanges["Foo"] instanceof ObjectAdded + } + def "interfaced renamed"() { given: def oldSdl = ''' @@ -131,10 +159,9 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def interfaceChanged = changes.findAll({ it instanceof InterfaceModified }) as List then: - interfaceChanged.size() == 1 - interfaceChanged[0].name == "Node2" + changes.interfaceChanges.size() == 1 + changes.interfaceChanges["Node2"] instanceof InterfaceModified } def "interfaced renamed and another interface added to it"() { @@ -167,11 +194,17 @@ class EditOperationAnalyzerTest extends Specification { ''' when: def changes = changes(oldSdl, newSdl) - def interfaceChanged = changes.findAll({ it instanceof InterfaceModified }) as List then: - interfaceChanged.size() == 1 - interfaceChanged.interfaceChangeDetails.size() == 1 - (interfaceChanged.interfaceChangeDetails[0] as InterfaceModified.AddedInterfaceToInterfaceDetail).name == "NewI" + changes.interfaceChanges.size() == 2 + changes.interfaceChanges["Node2"] instanceof InterfaceModified + changes.interfaceChanges["NewI"] instanceof InterfaceAdded + changes.objectChanges.size() == 1 + changes.objectChanges["Foo"] instanceof ObjectModified + def objectModified = changes.objectChanges["Foo"] as ObjectModified + def addedInterfaceDetails = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + addedInterfaceDetails.size() == 1 + addedInterfaceDetails[0].name == "NewI" + } From 7f5737c36e02c85bff9c1dc8d6a27bca7dde6436 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 9 Oct 2022 09:18:31 +1000 Subject: [PATCH 168/294] edit operation analyzer wip --- .../diffing/ana/EditOperationAnalyzer.java | 46 ++++++++++--------- .../schema/diffing/ana/ObjectModified.java | 17 +++++++ .../ana/EditOperationAnalyzerTest.groovy | 40 ++++++++-------- 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ee5ad8ca06..da14feb869 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -49,31 +49,33 @@ public EditOperationAnalyzer(GraphQLSchema oldSchema, public EditOperationAnalysisResult analyzeEdits(List editOperations) { handleTypeVertexChanges(editOperations); handleEdgeChanges(editOperations); -// for (EditOperation editOperation : editOperations) { -// switch (editOperation.getOperation()) { -// case INSERT_VERTEX: -// insertedVertex(editOperation); -// break; -// case DELETE_VERTEX: -// deletedVertex(editOperation); -// break; -// case CHANGE_VERTEX: -// changeVertex(editOperation); -// break; -// case INSERT_EDGE: -// insertedEdge(editOperation); -// break; -// case DELETE_EDGE: -// deletedEdge(editOperation); -// break; -// case CHANGE_EDGE: -// changedEdge(editOperation); -// break; -// } -// } + handleOtherChanges(editOperations); return new EditOperationAnalysisResult(objectChanges, interfaceChanges, unionChanges, enumChanges, inputObjectChanges, scalarChanges); } + private void handleOtherChanges(List editOperations) { + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case CHANGE_VERTEX: + if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { + fieldChanged(editOperation); + } + } + } + + } + + private void fieldChanged(EditOperation editOperation) { + Vertex field = editOperation.getTargetVertex(); + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); + String oldName = editOperation.getSourceVertex().getName(); + String newName = field.getName(); + objectModified.getObjectChangeDetails().add(new ObjectModified.FieldRenamed(oldName, newName)); + } + } + private void handleTypeVertexChanges(List editOperations) { for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java index 05f0ae8e41..d5ec6dc258 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -34,6 +34,23 @@ public String getName() { return name; } } + public static class FieldRenamed implements ObjectChangeDetail { + private final String oldName; + private final String newName; + + public FieldRenamed(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } public ObjectModified(String name) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a8cda76ebc..c74a097871 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -8,25 +8,27 @@ import static graphql.schema.diffing.ana.SchemaChanges.* class EditOperationAnalyzerTest extends Specification { -// def "test field changed"() { -// given: -// def oldSdl = ''' -// type Query { -// hello: String -// } -// ''' -// def newSdl = ''' -// type Query { -// hello2: String -// } -// ''' -// when: -// def changes = changes(oldSdl, newSdl) -// then: -// changes -// (changes[0] as FieldModified).name == "hello2" -// (changes[0] as FieldModified).fieldsContainer == "Query" -// } + def "field name changed"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello2: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModified + def objectModified = changes.objectChanges["Query"] as ObjectModified + def fieldRenames = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.FieldRenamed }) as List + fieldRenames[0].oldName == "hello" + fieldRenames[0].newName == "hello2" + } // // def "test field added"() { // given: From f3b10fb8056ccbf86c8fd02d40803805daa672f2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 9 Oct 2022 09:26:39 +1000 Subject: [PATCH 169/294] edit operation analyzer wip --- .../diffing/ana/EditOperationAnalyzer.java | 51 ++++++++---------- .../schema/diffing/ana/ObjectModified.java | 26 ++++++--- .../ana/EditOperationAnalyzerTest.groovy | 53 ++++++++++--------- 3 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index da14feb869..65d7719100 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -60,6 +60,11 @@ private void handleOtherChanges(List editOperations) { if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldChanged(editOperation); } + break; + case INSERT_VERTEX: + if(editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { + fieldAdded(editOperation); + } } } @@ -72,10 +77,24 @@ private void fieldChanged(EditOperation editOperation) { ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); String oldName = editOperation.getSourceVertex().getName(); String newName = field.getName(); - objectModified.getObjectChangeDetails().add(new ObjectModified.FieldRenamed(oldName, newName)); + objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldRenamed(oldName, newName)); + } + } + + private void fieldAdded(EditOperation editOperation) { + Vertex field = editOperation.getTargetVertex(); + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + if(isNewObject(fieldsContainerForField.getName())) { + return; + } + ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); + String name = field.getName(); + objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldAdded(name)); } } + private void handleTypeVertexChanges(List editOperations) { for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { @@ -196,7 +215,7 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { Vertex objectVertex = newEdge.getFrom(); Vertex interfaceVertex = newEdge.getTo(); ObjectModified.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModified.AddedInterfaceToObjectDetail(interfaceVertex.getName()); - getObjectModified(objectVertex.getName()).getObjectChangeDetails().add(addedInterfaceToObjectDetail); + getObjectModified(objectVertex.getName()).getObjectModifiedDetails().add(addedInterfaceToObjectDetail); } else if (from.isOfType(SchemaGraph.INTERFACE)) { if (isNewInterface(from.getName())) { @@ -235,21 +254,7 @@ private InterfaceModified getInterfaceModified(String newName) { assertTrue(interfaceChanges.get(newName) instanceof InterfaceModified); return (InterfaceModified) interfaceChanges.get(newName); } -//// -// private InterfaceChanged getInterfaceChanged(String newName) { -// if (!interfaceChangedMap.containsKey(newName)) { -// interfaceChangedMap.put(newName, new InterfaceChanged(newName)); -// } -// return interfaceChangedMap.get(newName); -// } - - private void deletedEdge(EditOperation editOperation) { - - } - private void changedEdge(EditOperation editOperation) { - - } private void addedObject(EditOperation editOperation) { @@ -298,19 +303,7 @@ private void addedScalar(EditOperation editOperation) { scalarChanges.put(scalarName, scalarAdded); } -// private void addedField(EditOperation editOperation) { -// Vertex newField = editOperation.getTargetVertex(); -// Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(newField); -// FieldAdded objectAdded = new FieldAdded(newField.getName(), fieldsContainerForField.getName()); -// changes.add(objectAdded); -// } -// -// private void addedInputField(EditOperation editOperation) { -// String objectName = editOperation.getTargetVertex().getName(); -// -// ObjectAdded objectAdded = new ObjectAdded(objectName); -// changes.add(objectAdded); -// } + private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java index d5ec6dc258..eaf7cc70df 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -6,13 +6,13 @@ public class ObjectModified implements SchemaChange.ObjectChange { private final String name; - private final List objectChangeDetails = new ArrayList<>(); + private final List objectModifiedDetails = new ArrayList<>(); - interface ObjectChangeDetail { + public interface ObjectModifiedDetails { } - public static class AddedInterfaceToObjectDetail implements ObjectChangeDetail { + public static class AddedInterfaceToObjectDetail implements ObjectModifiedDetails { private final String name; public AddedInterfaceToObjectDetail(String name) { @@ -23,7 +23,7 @@ public String getName() { return name; } } - public static class RemovedInterfaceToObjectDetail implements ObjectChangeDetail { + public static class RemovedInterfaceToObjectDetail implements ObjectModifiedDetails { private final String name; public RemovedInterfaceToObjectDetail(String name) { @@ -34,7 +34,7 @@ public String getName() { return name; } } - public static class FieldRenamed implements ObjectChangeDetail { + public static class FieldRenamed implements ObjectModifiedDetails { private final String oldName; private final String newName; @@ -51,6 +51,17 @@ public String getOldName() { return oldName; } } + public static class FieldAdded implements ObjectModifiedDetails { + private final String name; + + public FieldAdded(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } public ObjectModified(String name) { @@ -62,7 +73,8 @@ public String getName() { } // this will be mutated - public List getObjectChangeDetails() { - return objectChangeDetails; + + public List getObjectModifiedDetails() { + return objectModifiedDetails; } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index c74a097871..1c086a27fe 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -25,31 +25,33 @@ class EditOperationAnalyzerTest extends Specification { then: changes.objectChanges["Query"] instanceof ObjectModified def objectModified = changes.objectChanges["Query"] as ObjectModified - def fieldRenames = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.FieldRenamed }) as List + def fieldRenames = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.FieldRenamed }) as List fieldRenames[0].oldName == "hello" fieldRenames[0].newName == "hello2" } -// -// def "test field added"() { -// given: -// def oldSdl = ''' -// type Query { -// hello: String -// } -// ''' -// def newSdl = ''' -// type Query { -// hello: String -// newOne: String -// } -// ''' -// when: -// def changes = changes(oldSdl, newSdl) -// then: -// changes.size() == 1 -// (changes[0] as FieldAdded).name == "newOne" -// (changes[0] as FieldAdded).fieldsContainer == "Query" -// } + + def "test field added"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello: String + newOne: String + } + ''' + when: + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModified + def objectModified = changes.objectChanges["Query"] as ObjectModified + def fieldAdded = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.FieldAdded }) as List + fieldAdded[0].name == "newOne" + } def "test Object added"() { given: @@ -70,8 +72,7 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges.size() == 1 - changes.objectChanges["Foo"]instanceof ObjectAdded + changes.objectChanges["Foo"] instanceof ObjectAdded } def "new Interface introduced"() { @@ -103,7 +104,7 @@ class EditOperationAnalyzerTest extends Specification { changes.objectChanges.size() == 1 changes.objectChanges["Foo"] instanceof ObjectModified def objectModified = changes.objectChanges["Foo"] as ObjectModified - def addedInterfaceDetails = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + def addedInterfaceDetails = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" } @@ -203,7 +204,7 @@ class EditOperationAnalyzerTest extends Specification { changes.objectChanges.size() == 1 changes.objectChanges["Foo"] instanceof ObjectModified def objectModified = changes.objectChanges["Foo"] as ObjectModified - def addedInterfaceDetails = objectModified.objectChangeDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + def addedInterfaceDetails = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "NewI" From 8c947773944593c22f7f94b9aa6413801c3faebc Mon Sep 17 00:00:00 2001 From: bspeth Date: Sun, 9 Oct 2022 16:27:46 -0700 Subject: [PATCH 170/294] Patch SchemaDiff to apply respective nullability change validation for input vs. output types (#2971) * Fix schema diff for object vs input * rewrite to enumerate three top-level cases for old and inner cases for new * fix code style * Additional list_ness tests. Moved case Query.being( .. , newArg: String!) from schema_changed_object_fields to schema_changed_input_object_fields. Added case Istari.temperament "non-null to nullable" -> stricter. Co-authored-by: Brendan Speth --- .../java/graphql/schema/diff/SchemaDiff.java | 100 +++++++++++++----- .../graphql/schema/diff/SchemaDiffTest.groovy | 96 ++++++++++++----- .../resources/diff/schema_ABaseLine.graphqls | 3 +- .../schema_changed_field_arguments.graphqls | 3 +- ...chema_changed_input_object_fields.graphqls | 11 +- ...hanged_nested_input_object_fields.graphqls | 3 +- .../schema_changed_object_fields.graphqls | 24 +++-- .../diff/schema_changed_type_kind.graphqls | 3 +- .../diff/schema_dangerous_changes.graphqls | 3 +- .../schema_deprecated_fields_new.graphqls | 1 + .../schema_interface_fields_missing.graphqls | 3 +- .../diff/schema_missing_enum_value.graphqls | 3 +- .../schema_missing_field_arguments.graphqls | 3 +- ...chema_missing_input_object_fields.graphqls | 3 +- .../schema_missing_object_fields.graphqls | 3 +- .../diff/schema_missing_operation.graphqls | 3 +- .../schema_missing_union_members.graphqls | 3 +- .../schema_with_additional_field.graphqls | 3 +- 18 files changed, 199 insertions(+), 72 deletions(-) diff --git a/src/main/java/graphql/schema/diff/SchemaDiff.java b/src/main/java/graphql/schema/diff/SchemaDiff.java index b89616bc40..18e34967f9 100644 --- a/src/main/java/graphql/schema/diff/SchemaDiff.java +++ b/src/main/java/graphql/schema/diff/SchemaDiff.java @@ -397,7 +397,7 @@ private void checkInputFields(DiffCtx ctx, TypeDefinition old, List oldDirecti } } - DiffCategory checkTypeWithNonNullAndList(Type oldType, Type newType) { + DiffCategory checkTypeWithNonNullAndListOnInputOrArg(Type oldType, Type newType) { TypeInfo oldTypeInfo = typeInfo(oldType); TypeInfo newTypeInfo = typeInfo(newType); @@ -840,31 +840,83 @@ DiffCategory checkTypeWithNonNullAndList(Type oldType, Type newType) { } while (true) { - // - // its allowed to get more less strict in the new but not more strict - if (oldTypeInfo.isNonNull() && newTypeInfo.isNonNull()) { - oldTypeInfo = oldTypeInfo.unwrapOne(); - newTypeInfo = newTypeInfo.unwrapOne(); - } else if (oldTypeInfo.isNonNull() && !newTypeInfo.isNonNull()) { - oldTypeInfo = oldTypeInfo.unwrapOne(); - } else if (!oldTypeInfo.isNonNull() && newTypeInfo.isNonNull()) { - return DiffCategory.STRICTER; - } - // lists - if (oldTypeInfo.isList() && !newTypeInfo.isList()) { - return DiffCategory.INVALID; + if (oldTypeInfo.isNonNull()) { + if (newTypeInfo.isNonNull()) { + // if they're both non-null, compare the unwrapped types + oldTypeInfo = oldTypeInfo.unwrapOne(); + newTypeInfo = newTypeInfo.unwrapOne(); + } else { + // non-null to nullable is valid, as long as the underlying types are also valid + oldTypeInfo = oldTypeInfo.unwrapOne(); + } + } else if (oldTypeInfo.isList()) { + if (newTypeInfo.isList()) { + // if they're both lists, compare the unwrapped types + oldTypeInfo = oldTypeInfo.unwrapOne(); + newTypeInfo = newTypeInfo.unwrapOne(); + } else if (newTypeInfo.isNonNull()) { + // nullable to non-null creates a stricter input requirement for clients to specify + return DiffCategory.STRICTER; + } else { + // list to non-list is not valid + return DiffCategory.INVALID; + } + } else { + if (newTypeInfo.isNonNull()) { + // nullable to non-null creates a stricter input requirement for clients to specify + return DiffCategory.STRICTER; + } else if (newTypeInfo.isList()) { + // non-list to list is not valid + return DiffCategory.INVALID; + } else { + return null; + } } - // plain - if (oldTypeInfo.isPlain()) { - if (!newTypeInfo.isPlain()) { + } + } + + DiffCategory checkTypeWithNonNullAndListOnObjectOrInterface(Type oldType, Type newType) { + TypeInfo oldTypeInfo = typeInfo(oldType); + TypeInfo newTypeInfo = typeInfo(newType); + + if (!oldTypeInfo.getName().equals(newTypeInfo.getName())) { + return DiffCategory.INVALID; + } + + while (true) { + if (oldTypeInfo.isNonNull()) { + if (newTypeInfo.isNonNull()) { + // if they're both non-null, compare the unwrapped types + oldTypeInfo = oldTypeInfo.unwrapOne(); + newTypeInfo = newTypeInfo.unwrapOne(); + } else { + // non-null to nullable requires a stricter check from clients since it removes the guarantee of presence + return DiffCategory.STRICTER; + } + } else if (oldTypeInfo.isList()) { + if (newTypeInfo.isList()) { + // if they're both lists, compare the unwrapped types + oldTypeInfo = oldTypeInfo.unwrapOne(); + newTypeInfo = newTypeInfo.unwrapOne(); + } else if (newTypeInfo.isNonNull()) { + // nullable to non-null is valid, as long as the underlying types are also valid + newTypeInfo = newTypeInfo.unwrapOne(); + } else { + // list to non-list is not valid return DiffCategory.INVALID; } - break; + } else { + if (newTypeInfo.isNonNull()) { + // nullable to non-null is valid, as long as the underlying types are also valid + newTypeInfo = newTypeInfo.unwrapOne(); + } else if (newTypeInfo.isList()) { + // non-list to list is not valid + return DiffCategory.INVALID; + } else { + return null; + } } - oldTypeInfo = oldTypeInfo.unwrapOne(); - newTypeInfo = newTypeInfo.unwrapOne(); } - return null; } diff --git a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy index 07d1350258..308e9c46f1 100644 --- a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy +++ b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy @@ -105,7 +105,7 @@ class SchemaDiffTest extends Specification { } - def "change_in_null_ness"() { + def "change_in_null_ness_input_or_arg"() { given: Type baseLine = new NonNullType(new ListType(new TypeName("foo"))) @@ -116,12 +116,12 @@ class SchemaDiffTest extends Specification { def diff = new SchemaDiff() - def sameType = diff.checkTypeWithNonNullAndList(baseLine, same) + def sameType = diff.checkTypeWithNonNullAndListOnInputOrArg(baseLine, same) - def lessStrict = diff.checkTypeWithNonNullAndList(baseLine, less) + def lessStrict = diff.checkTypeWithNonNullAndListOnInputOrArg(baseLine, less) // not allowed as old clients wont work - def moreStrict = diff.checkTypeWithNonNullAndList(less, baseLine) + def moreStrict = diff.checkTypeWithNonNullAndListOnInputOrArg(less, baseLine) expect: @@ -130,18 +130,60 @@ class SchemaDiffTest extends Specification { moreStrict == DiffCategory.STRICTER } - def "change_in_list_ness"() { + def "change_in_null_ness_object_or_interface"() { given: - Type baseLine = new NonNullType(new ListType(new TypeName("foo"))) + Type nonNull = new NonNullType(new ListType(new TypeName("foo"))) + Type nonNullDuplicate = new NonNullType(new ListType(new TypeName("foo"))) + + Type nullable = new ListType(new TypeName("foo")) + + + def diff = new SchemaDiff() + + def sameType = diff.checkTypeWithNonNullAndListOnObjectOrInterface(nonNull, nonNullDuplicate) + + def removeGuarantee = diff.checkTypeWithNonNullAndListOnObjectOrInterface(nonNull, nullable) + + def addGuarantee = diff.checkTypeWithNonNullAndListOnObjectOrInterface(nullable, nonNull) + + + expect: + sameType == null + removeGuarantee == DiffCategory.STRICTER + addGuarantee == null + } + + def "change_in_list_ness_input_or_arg"() { + + given: + Type list = new NonNullType(new ListType(new TypeName("foo"))) Type notList = new NonNullType(new TypeName("foo")) def diff = new SchemaDiff() - def noLongerList = diff.checkTypeWithNonNullAndList(baseLine, notList) + def noLongerList = diff.checkTypeWithNonNullAndListOnInputOrArg(list, notList) + def nowList = diff.checkTypeWithNonNullAndListOnInputOrArg(notList, list) expect: noLongerList == DiffCategory.INVALID + nowList == DiffCategory.INVALID + } + + def "change_in_list_ness_object_or_interface"() { + + given: + Type list = new NonNullType(new ListType(new TypeName("foo"))) + Type notList = new NonNullType(new TypeName("foo")) + + def diff = new SchemaDiff() + + def noLongerList = diff.checkTypeWithNonNullAndListOnObjectOrInterface(list, notList) + def nowList = diff.checkTypeWithNonNullAndListOnObjectOrInterface(list, notList) + + expect: + noLongerList == DiffCategory.INVALID + nowList == DiffCategory.INVALID } DiffEvent lastBreakage(CapturingReporter capturingReporter) { @@ -344,16 +386,26 @@ class SchemaDiffTest extends Specification { diff.diffSchema(diffSet, chainedReporter) expect: - reporter.breakageCount == 2 - reporter.breakages[0].category == DiffCategory.INVALID - reporter.breakages[0].typeName == 'Questor' - reporter.breakages[0].typeKind == TypeKind.InputObject - reporter.breakages[0].fieldName == 'queryTarget' + reporter.breakageCount == 4 + reporter.breakages[0].category == DiffCategory.STRICTER + reporter.breakages[0].typeName == 'Query' + reporter.breakages[0].typeKind == TypeKind.Object + reporter.breakages[0].fieldName == 'being' reporter.breakages[1].category == DiffCategory.STRICTER reporter.breakages[1].typeName == 'Questor' reporter.breakages[1].typeKind == TypeKind.InputObject - reporter.breakages[1].fieldName == 'newMandatoryField' + reporter.breakages[1].fieldName == 'nestedInput' + + reporter.breakages[2].category == DiffCategory.INVALID + reporter.breakages[2].typeName == 'Questor' + reporter.breakages[2].typeKind == TypeKind.InputObject + reporter.breakages[2].fieldName == 'queryTarget' + + reporter.breakages[3].category == DiffCategory.STRICTER + reporter.breakages[3].typeName == 'Questor' + reporter.breakages[3].typeKind == TypeKind.InputObject + reporter.breakages[3].fieldName == 'newMandatoryField' } @@ -433,27 +485,21 @@ class SchemaDiffTest extends Specification { diff.diffSchema(diffSet, chainedReporter) expect: - reporter.breakageCount == 4 + reporter.breakageCount == 3 reporter.breakages[0].category == DiffCategory.STRICTER - reporter.breakages[0].typeName == 'Query' + reporter.breakages[0].typeName == 'Istari' reporter.breakages[0].typeKind == TypeKind.Object - reporter.breakages[0].fieldName == 'being' + reporter.breakages[0].fieldName == 'temperament' reporter.breakages[1].category == DiffCategory.INVALID reporter.breakages[1].typeName == 'Query' reporter.breakages[1].typeKind == TypeKind.Object reporter.breakages[1].fieldName == 'beings' - reporter.breakages[2].category == DiffCategory.STRICTER + reporter.breakages[2].category == DiffCategory.INVALID reporter.breakages[2].typeName == 'Query' reporter.breakages[2].typeKind == TypeKind.Object reporter.breakages[2].fieldName == 'customScalar' - - reporter.breakages[3].category == DiffCategory.STRICTER - reporter.breakages[3].typeName == 'Query' - reporter.breakages[3].typeKind == TypeKind.Object - reporter.breakages[3].fieldName == 'wizards' - } def "dangerous changes"() { @@ -504,7 +550,7 @@ class SchemaDiffTest extends Specification { diff.diffSchema(diffSet, chainedReporter) expect: - reporter.dangerCount == 13 + reporter.dangerCount == 14 reporter.breakageCount == 0 reporter.dangers.every { it.getCategory() == DiffCategory.DEPRECATION_ADDED @@ -523,7 +569,7 @@ class SchemaDiffTest extends Specification { expect: reporter.dangerCount == 0 - reporter.breakageCount == 11 + reporter.breakageCount == 12 reporter.breakages.every { it.getCategory() == DiffCategory.DEPRECATION_REMOVED } diff --git a/src/test/resources/diff/schema_ABaseLine.graphqls b/src/test/resources/diff/schema_ABaseLine.graphqls index 7444225c96..59806923f1 100644 --- a/src/test/resources/diff/schema_ABaseLine.graphqls +++ b/src/test/resources/diff/schema_ABaseLine.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_changed_field_arguments.graphqls b/src/test/resources/diff/schema_changed_field_arguments.graphqls index 43c919dd33..e8c0c260fe 100644 --- a/src/test/resources/diff/schema_changed_field_arguments.graphqls +++ b/src/test/resources/diff/schema_changed_field_arguments.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -23,7 +24,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_changed_input_object_fields.graphqls b/src/test/resources/diff/schema_changed_input_object_fields.graphqls index 7d6af1b074..d7d026ffa8 100644 --- a/src/test/resources/diff/schema_changed_input_object_fields.graphqls +++ b/src/test/resources/diff/schema_changed_input_object_fields.graphqls @@ -4,7 +4,8 @@ schema { } type Query { - being(id : ID, type : String = "wizard") : Being + #being(id : ID, type : String = "wizard") : Being + being(id : ID, type : String, newArg : String!) : Being beings(type : String) : [Being] wizards : [Istari] @@ -12,6 +13,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,9 +24,14 @@ type Mutation { } input Questor { + #beingID : ID! beingID : ID + + #queryTarget : String queryTarget : Int - nestedInput : NestedInput + + #nestedInput : NestedInput + nestedInput : NestedInput! newMandatoryField : String! } diff --git a/src/test/resources/diff/schema_changed_nested_input_object_fields.graphqls b/src/test/resources/diff/schema_changed_nested_input_object_fields.graphqls index 1ac57e90c5..d3009f7db7 100644 --- a/src/test/resources/diff/schema_changed_nested_input_object_fields.graphqls +++ b/src/test/resources/diff/schema_changed_nested_input_object_fields.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_changed_object_fields.graphqls b/src/test/resources/diff/schema_changed_object_fields.graphqls index d7a86306de..4c5f273dca 100644 --- a/src/test/resources/diff/schema_changed_object_fields.graphqls +++ b/src/test/resources/diff/schema_changed_object_fields.graphqls @@ -4,8 +4,7 @@ schema { } type Query { - #being(id : ID, type : String = "wizard") : Being - being(id : ID, type : String, newArg : String!) : Being + being(id : ID, type : String = "wizard") : Being #beings(type : String) : [Being] beings(type : String) : Being @@ -18,6 +17,9 @@ type Query { #allCharacters : [Character!] @deprecated(reason: "no longer supported") allCharacters : [Character!] + #allCharactersByTemperament : [[Character]] + allCharactersByTemperament : [[Character!]]! + #customScalar : CustomScalar customScalar : [CustomScalar]! } @@ -28,7 +30,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } @@ -41,7 +43,8 @@ scalar CustomScalar interface Being { - id : ID + #id : ID + id : ID! name : String nameInQuenyan : String invitedBy(id : ID) : Being @@ -49,7 +52,8 @@ interface Being { type Ainur implements Being { - id : ID + #id : ID + id : ID! name : String nameInQuenyan : String invitedBy(id : ID) : Being @@ -58,17 +62,21 @@ type Ainur implements Being { type Istari implements Being { - id : ID + #id : ID + id : ID! name : String nameInQuenyan : String invitedBy(id : ID) : Being colour : String - temperament : Temperament! + + #temperament : Temperament! + temperament : Temperament } type Deity implements Being { - id : ID + #id : ID + id : ID! name : String nameInQuenyan : String invitedBy(id : ID) : Being diff --git a/src/test/resources/diff/schema_changed_type_kind.graphqls b/src/test/resources/diff/schema_changed_type_kind.graphqls index 5107c3ed39..14e85658f9 100644 --- a/src/test/resources/diff/schema_changed_type_kind.graphqls +++ b/src/test/resources/diff/schema_changed_type_kind.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_dangerous_changes.graphqls b/src/test/resources/diff/schema_dangerous_changes.graphqls index cb910f80c0..1fa8b8808e 100644 --- a/src/test/resources/diff/schema_dangerous_changes.graphqls +++ b/src/test/resources/diff/schema_dangerous_changes.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_deprecated_fields_new.graphqls b/src/test/resources/diff/schema_deprecated_fields_new.graphqls index b3d92e6d15..53dc321ede 100644 --- a/src/test/resources/diff/schema_deprecated_fields_new.graphqls +++ b/src/test/resources/diff/schema_deprecated_fields_new.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] @deprecated allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] @deprecated(reason: "remove all Character references") customScalar : CustomScalar @deprecated(reason: "because") } diff --git a/src/test/resources/diff/schema_interface_fields_missing.graphqls b/src/test/resources/diff/schema_interface_fields_missing.graphqls index b8e0f043fb..15f45aacdf 100644 --- a/src/test/resources/diff/schema_interface_fields_missing.graphqls +++ b/src/test/resources/diff/schema_interface_fields_missing.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_enum_value.graphqls b/src/test/resources/diff/schema_missing_enum_value.graphqls index f69185f429..7dcf130ab5 100644 --- a/src/test/resources/diff/schema_missing_enum_value.graphqls +++ b/src/test/resources/diff/schema_missing_enum_value.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_field_arguments.graphqls b/src/test/resources/diff/schema_missing_field_arguments.graphqls index 40c5ad21e9..1657a86eed 100644 --- a/src/test/resources/diff/schema_missing_field_arguments.graphqls +++ b/src/test/resources/diff/schema_missing_field_arguments.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -24,7 +25,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_input_object_fields.graphqls b/src/test/resources/diff/schema_missing_input_object_fields.graphqls index 543e4f4048..c06aa426d4 100644 --- a/src/test/resources/diff/schema_missing_input_object_fields.graphqls +++ b/src/test/resources/diff/schema_missing_input_object_fields.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! #queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_object_fields.graphqls b/src/test/resources/diff/schema_missing_object_fields.graphqls index d033ac0a1d..073de2507d 100644 --- a/src/test/resources/diff/schema_missing_object_fields.graphqls +++ b/src/test/resources/diff/schema_missing_object_fields.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_operation.graphqls b/src/test/resources/diff/schema_missing_operation.graphqls index 9e83a2eb6a..7b4ac24077 100644 --- a/src/test/resources/diff/schema_missing_operation.graphqls +++ b/src/test/resources/diff/schema_missing_operation.graphqls @@ -11,12 +11,13 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_missing_union_members.graphqls b/src/test/resources/diff/schema_missing_union_members.graphqls index dd6ea96050..51c3d2ea6a 100644 --- a/src/test/resources/diff/schema_missing_union_members.graphqls +++ b/src/test/resources/diff/schema_missing_union_members.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } diff --git a/src/test/resources/diff/schema_with_additional_field.graphqls b/src/test/resources/diff/schema_with_additional_field.graphqls index 2001ba2f75..1e9c4daa1c 100644 --- a/src/test/resources/diff/schema_with_additional_field.graphqls +++ b/src/test/resources/diff/schema_with_additional_field.graphqls @@ -12,6 +12,7 @@ type Query { deities : [Deity] allCharacters : [Character!] @deprecated(reason: "no longer supported") + allCharactersByTemperament : [[Character]] customScalar : CustomScalar } @@ -22,7 +23,7 @@ type Mutation { } input Questor { - beingID : ID + beingID : ID! queryTarget : String nestedInput : NestedInput } From 4d7bc533fe9cdd64ce6c160142d58a74d1f49334 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 10 Oct 2022 15:42:59 +1000 Subject: [PATCH 171/294] old schema diff test --- .../graphql/schema/diff/SchemaDiffTest.groovy | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy index e1ff7c5124..ec0972ce7d 100644 --- a/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy +++ b/src/test/groovy/graphql/schema/diff/SchemaDiffTest.groovy @@ -558,4 +558,66 @@ class SchemaDiffTest extends Specification { } + def "field renamed"() { + def oldSchema = TestUtil.schema(''' + type Query { + hello: String + } + ''') + def newSchema = TestUtil.schema(''' + type Query { + hello2: String + } + ''') + def reporter = new CapturingReporter() + DiffSet diffSet = DiffSet.diffSet(oldSchema, newSchema) + def diff = new SchemaDiff() + when: + diff.diffSchema(diffSet, reporter) + + then: + // the old hello field is missing + reporter.breakageCount == 1 + reporter.breakages.every { + it.getCategory() == DiffCategory.MISSING + } + + } + def "interface renamed"() { + def oldSchema = TestUtil.schema(''' + type Query implements Hello{ + hello: String + world: World + } + type World implements Hello { + hello: String + } + interface Hello { + hello: String + } + ''') + def newSchema = TestUtil.schema(''' + type Query implements Hello2{ + hello: String + world: World + } + type World implements Hello2 { + hello: String + } + interface Hello2 { + hello: String + } + ''') + def reporter = new CapturingReporter() + DiffSet diffSet = DiffSet.diffSet(oldSchema, newSchema) + def diff = new SchemaDiff() + when: + diff.diffSchema(diffSet, reporter) + + then: + // two breakages for World and Query not implementing Hello anymore + reporter.breakageCount == 2 + + } + } From 7a3f7eb3f22174121381916f4e5c4d872ca57e37 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 05:18:39 +1000 Subject: [PATCH 172/294] add schema vertex --- .../java/graphql/schema/diffing/DiffImpl.java | 12 ++--- .../diffing/FillupIsolatedVertices.java | 17 +++++++ .../graphql/schema/diffing/SchemaDiffing.java | 3 -- .../graphql/schema/diffing/SchemaGraph.java | 1 + .../schema/diffing/SchemaGraphFactory.java | 45 ++++++++++++++----- .../schema/diffing/SchemaDiffingTest.groovy | 33 +++++++++++++- 6 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 5c202f35ba..559733be45 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -17,6 +18,7 @@ import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; +import static java.util.Collections.singletonList; public class DiffImpl { @@ -73,15 +75,15 @@ public DiffImpl(SchemaGraph completeSourceGraph, SchemaGraph completeTargetGraph OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List relevantTargetList) throws Exception { - OptimalEdit optimalEdit = new OptimalEdit(); - int graphSize = relevantSourceList.size(); - int mappingCost = editorialCostForMapping(startMapping, completeSourceGraph, completeTargetGraph, new ArrayList<>()); + ArrayList initialEditOperations = new ArrayList<>(); + int mappingCost = editorialCostForMapping(startMapping, completeSourceGraph, completeTargetGraph, initialEditOperations); int level = startMapping.size(); MappingEntry firstMappingEntry = new MappingEntry(startMapping, level, mappingCost); System.out.println("first entry: lower bound: " + mappingCost + " at level " + level); + OptimalEdit optimalEdit = new OptimalEdit(); PriorityQueue queue = new PriorityQueue<>((mappingEntry1, mappingEntry2) -> { int compareResult = Double.compare(mappingEntry1.lowerBoundCost, mappingEntry2.lowerBoundCost); if (compareResult == 0) { @@ -376,9 +378,9 @@ private double calcLowerBoundMappingCost(Vertex v, anchoredVerticesCost++; } - Edge sourceEdgeInverse = completeSourceGraph.getEdge(vPrime,v); + Edge sourceEdgeInverse = completeSourceGraph.getEdge(vPrime, v); String labelSourceEdgeInverse = sourceEdgeInverse != null ? sourceEdgeInverse.getLabel() : null; - Edge targetEdgeInverse = completeTargetGraph.getEdge(mappedVPrime,u); + Edge targetEdgeInverse = completeTargetGraph.getEdge(mappedVPrime, u); String labelTargetEdgeInverse = targetEdgeInverse != null ? targetEdgeInverse.getLabel() : null; if (!Objects.equals(labelSourceEdgeInverse, labelTargetEdgeInverse)) { anchoredVerticesCost++; diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 7b0c236b70..a8cdbfef0e 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -33,6 +33,7 @@ import static graphql.schema.diffing.SchemaGraph.INTERFACE; import static graphql.schema.diffing.SchemaGraph.OBJECT; import static graphql.schema.diffing.SchemaGraph.SCALAR; +import static graphql.schema.diffing.SchemaGraph.SCHEMA; import static graphql.schema.diffing.SchemaGraph.UNION; import static graphql.util.FpKit.concat; import static java.util.Collections.emptyList; @@ -49,6 +50,7 @@ public class FillupIsolatedVertices { static Map> typeContexts = new LinkedHashMap<>(); static { + typeContexts.put(SCHEMA, schemaContext()); typeContexts.put(FIELD, fieldContext()); typeContexts.put(ARGUMENT, argumentsContexts()); typeContexts.put(INPUT_FIELD, inputFieldContexts()); @@ -633,6 +635,20 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { return contexts; } + private static List schemaContext() { + VertexContextSegment schema = new VertexContextSegment() { + @Override + public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { + return vertex.getType(); + } + + @Override + public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { + return SCHEMA.equals(vertex.getType()); + } + }; + return singletonList(schema); + } private static List fieldContext() { VertexContextSegment field = new VertexContextSegment() { @Override @@ -741,6 +757,7 @@ public FillupIsolatedVertices(SchemaGraph sourceGraph, SchemaGraph targetGraph) } public void ensureGraphAreSameSize() { + calcPossibleMappings(typeContexts.get(SCHEMA), SCHEMA); calcPossibleMappings(typeContexts.get(FIELD), FIELD); calcPossibleMappings(typeContexts.get(ARGUMENT), ARGUMENT); calcPossibleMappings(typeContexts.get(INPUT_FIELD), INPUT_FIELD); diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index 6433e3bbb1..ec5411c5ad 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -3,13 +3,10 @@ import graphql.schema.GraphQLSchema; import graphql.schema.diffing.ana.EditOperationAnalysisResult; import graphql.schema.diffing.ana.EditOperationAnalyzer; -import graphql.schema.diffing.ana.SchemaChange; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 819ecd9309..2da503ffb1 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -23,6 +23,7 @@ public class SchemaGraph { + public static final String SCHEMA = "Schema"; public static final String OBJECT = "Object"; public static final String INTERFACE = "Interface"; public static final String UNION = "Union"; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 269db3ae73..8f7e2a7108 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -95,6 +95,7 @@ public TraversalControl leave(TraverserContext context) { return TraversalControl.CONTINUE; } }); + addSchemaVertex(schemaGraph, schema); ArrayList copyOfVertices = new ArrayList<>(schemaGraph.getVertices()); for (Vertex vertex : copyOfVertices) { @@ -117,6 +118,25 @@ public TraversalControl leave(TraverserContext context) { return schemaGraph; } + private void addSchemaVertex(SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLObjectType queryType = graphQLSchema.getQueryType(); + GraphQLObjectType mutationType = graphQLSchema.getMutationType(); + GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType(); + Vertex schemaVertex = new Vertex(SchemaGraph.SCHEMA, "schema"); + schemaGraph.addVertex(schemaVertex); + + Vertex queryVertex = schemaGraph.getType(queryType.getName()); + schemaGraph.addEdge(new Edge(schemaVertex, queryVertex, "query")); + if (mutationType != null) { + Vertex mutationVertex = schemaGraph.getType(mutationType.getName()); + schemaGraph.addEdge(new Edge(schemaVertex, mutationVertex, "mutation")); + } + if (subscriptionType != null) { + Vertex subscriptionVertex = schemaGraph.getType(subscriptionType.getName()); + schemaGraph.addEdge(new Edge(schemaVertex, subscriptionVertex, "subscription")); + } + } + private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { // Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); // schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); @@ -229,7 +249,7 @@ private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGr } schemaGraph.addVertex(objectVertex); schemaGraph.addType(graphQLObjectType.getName(), objectVertex); - cratedAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); + createAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph); } private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) { @@ -242,7 +262,7 @@ private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGra schemaGraph.addVertex(argumentVertex); schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex)); } - cratedAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph); + createAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph); return fieldVertex; } @@ -254,7 +274,7 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr if (graphQLArgument.hasSetDefaultValue()) { vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType()))); } - cratedAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); + createAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); return vertex; } @@ -269,7 +289,7 @@ private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, bo scalarVertex.add("specifiedByUrl", scalarType.getSpecifiedByUrl()); schemaGraph.addVertex(scalarVertex); schemaGraph.addType(scalarType.getName(), scalarVertex); - cratedAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); + createAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph); } private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { @@ -284,7 +304,7 @@ private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schema } schemaGraph.addVertex(interfaceVertex); schemaGraph.addType(interfaceType.getName(), interfaceVertex); - cratedAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); + createAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph); } private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { @@ -298,11 +318,11 @@ private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean enumValueVertex.add("name", enumValue.getName()); schemaGraph.addVertex(enumValueVertex); schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex)); - cratedAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph); + createAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph); } schemaGraph.addVertex(enumVertex); schemaGraph.addType(enumType.getName(), enumVertex); - cratedAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); + createAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph); } private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) { @@ -312,7 +332,7 @@ private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boole unionVertex.add("description", desc(unionType.getDescription())); schemaGraph.addVertex(unionVertex); schemaGraph.addType(unionType.getName(), unionVertex); - cratedAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); + createAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph); } private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) { @@ -327,11 +347,12 @@ private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph sche } schemaGraph.addVertex(inputObjectVertex); schemaGraph.addType(inputObject.getName(), inputObjectVertex); - cratedAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph); + createAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph); } - private void cratedAppliedDirectives(Vertex from, List appliedDirectives, SchemaGraph - schemaGraph) { + private void createAppliedDirectives(Vertex from, + List appliedDirectives, + SchemaGraph schemaGraph) { Map countByName = new LinkedHashMap<>(); for (GraphQLDirective appliedDirective : appliedDirectives) { Vertex appliedDirectiveVertex = new Vertex(SchemaGraph.APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++)); @@ -381,7 +402,7 @@ private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph sch if (inputField.hasSetDefaultValue()) { vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType()))); } - cratedAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); + createAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); return vertex; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 134ada5d36..bc3619b161 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -27,7 +27,7 @@ class SchemaDiffingTest extends Specification { def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: - schemaGraph.size() == 91 + schemaGraph.size() == 92 } @@ -1257,6 +1257,37 @@ class SchemaDiffingTest extends Specification { then: operations.size() == 0 } + + def "changed query operation type "() { + given: + def schema1 = schema(''' + type Query { + foo: String + } + type MyQuery { + foo: String + } + ''') + def schema2 = schema(''' + schema { + query: MyQuery + } + type Query { + foo: String + } + type MyQuery { + foo: String + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + // delete edge and insert new one + operations.size() == 2 + } } From 29d38cf44cba85443874c7e817d6e6ccc87a90b2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 05:31:09 +1000 Subject: [PATCH 173/294] schema directives --- .../diffing/FillupIsolatedVertices.java | 6 ++++ .../schema/diffing/SchemaGraphFactory.java | 2 ++ .../schema/diffing/SchemaDiffingTest.groovy | 33 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index a8cdbfef0e..7000605636 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -427,6 +427,8 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { + case SCHEMA: + return SCHEMA; case FIELD: Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container); return fieldsContainer.getType() + "." + fieldsContainer.getName(); @@ -467,6 +469,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { + case SCHEMA: case FIELD: case OBJECT: case INTERFACE: @@ -548,6 +551,8 @@ public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { + case SCHEMA: + return SCHEMA; case FIELD: Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container); return fieldsContainer.getType() + "." + fieldsContainer.getName(); @@ -589,6 +594,7 @@ public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) { Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { + case SCHEMA: case FIELD: case OBJECT: case INTERFACE: diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 8f7e2a7108..8d06289fcb 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -123,6 +123,7 @@ private void addSchemaVertex(SchemaGraph schemaGraph, GraphQLSchema graphQLSchem GraphQLObjectType mutationType = graphQLSchema.getMutationType(); GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType(); Vertex schemaVertex = new Vertex(SchemaGraph.SCHEMA, "schema"); + schemaVertex.add("name", SchemaGraph.SCHEMA); schemaGraph.addVertex(schemaVertex); Vertex queryVertex = schemaGraph.getType(queryType.getName()); @@ -135,6 +136,7 @@ private void addSchemaVertex(SchemaGraph schemaGraph, GraphQLSchema graphQLSchem Vertex subscriptionVertex = schemaGraph.getType(subscriptionType.getName()); schemaGraph.addEdge(new Edge(schemaVertex, subscriptionVertex, "subscription")); } + createAppliedDirectives(schemaVertex, graphQLSchema.getSchemaDirectives(), schemaGraph); } private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index bc3619b161..1e0d409533 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1288,6 +1288,39 @@ class SchemaDiffingTest extends Specification { // delete edge and insert new one operations.size() == 2 } + + def "applied schema directives"() { + given: + def schema1 = schema(''' + directive @foo(arg: String) on SCHEMA + + schema @foo(arg: "bar") { + query: MyQuery + } + type MyQuery { + foo: String + } + ''') + def schema2 = schema(''' + directive @foo(arg: String) on SCHEMA + + schema @foo(arg: "barChanged") { + query: MyQuery + } + type MyQuery { + foo: String + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + // applied argument changed + operations.size() == 1 + + } } From c372a95eda7b5f4fa9569007198ba4c221bd6bcf Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 18:21:15 +1000 Subject: [PATCH 174/294] cleanup --- .../java/graphql/schema/diffing/SchemaGraphFactory.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 8d06289fcb..e9ec113a70 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -111,9 +111,6 @@ public TraversalControl leave(TraverserContext context) { if (SchemaGraph.INPUT_OBJECT.equals(vertex.getType())) { handleInputObject(vertex, schemaGraph, schema); } - if (SchemaGraph.APPLIED_DIRECTIVE.equals(vertex.getType())) { - handleAppliedDirective(vertex, schemaGraph, schema); - } } return schemaGraph; } @@ -139,11 +136,6 @@ private void addSchemaVertex(SchemaGraph schemaGraph, GraphQLSchema graphQLSchem createAppliedDirectives(schemaVertex, graphQLSchema.getSchemaDirectives(), schemaGraph); } - private void handleAppliedDirective(Vertex appliedDirectiveVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { -// Vertex directiveVertex = schemaGraph.getDirective(appliedDirectiveVertex.get("name")); -// schemaGraph.addEdge(new Edge(appliedDirectiveVertex, directiveVertex)); - } - private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) graphQLSchema.getType(inputObject.get("name")); List inputFields = inputObjectType.getFields(); From 702fb4e2d2e7efc42c8276f6de4a4557a18dcb00 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 18:38:20 +1000 Subject: [PATCH 175/294] descriptions --- .../schema/diffing/SchemaGraphFactory.java | 2 +- .../schema/diffing/SchemaDiffingTest.groovy | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index e9ec113a70..6a4d6fd081 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -401,7 +401,7 @@ private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph sch } private String desc(String desc) { - return ""; + return desc; // return desc != null ? desc.replace("\n", "\\n") : null; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 1e0d409533..41e457ac56 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1321,6 +1321,32 @@ class SchemaDiffingTest extends Specification { operations.size() == 1 } + + def "change description"() { + given: + def schema1 = schema(''' + "Hello World" + type Query { + "helloDesc" + hello: String + } + ''') + def schema2 = schema(''' + "Hello World now" + type Query { + "helloDescChanged" + hello: String + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 2 + + } } From 669b1f0d0f30f49bb1296b3082f6b892cfaad0ba Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 18:43:57 +1000 Subject: [PATCH 176/294] testing --- .../schema/diffing/SchemaDiffingTest.groovy | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 41e457ac56..585234468f 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -51,6 +51,7 @@ class SchemaDiffingTest extends Specification { diff.size() == 1 } + def "test rename field 2"() { given: def schema1 = schema(""" @@ -1347,6 +1348,59 @@ class SchemaDiffingTest extends Specification { operations.size() == 2 } + + def "change default value"() { + given: + def schema1 = schema(''' + input I { + someNumber: Int = 100 + } + type Query { + hello(arg: String = "defaultValue", i: I): String + } + ''') + def schema2 = schema(''' + input I { + someNumber: Int = 200 + } + type Query { + hello(arg: String = "defaultValueChanged",i: I): String + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 2 + + } + + def "change field type, but not the wrapped type "() { + given: + def schema1 = schema(''' + type Query { + hello: String + hello2: String + } + ''') + def schema2 = schema(''' + type Query { + hello: String! + hello2: [[String!]] + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 2 + operations.findAll({ it.operation == EditOperation.Operation.CHANGE_EDGE }).size() == 2 + + } } From b24e0822b201bcc094e692555c246ef80c07d658 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 18:55:44 +1000 Subject: [PATCH 177/294] default values --- .../schema/diffing/SchemaGraphFactory.java | 23 +++++++------ .../schema/diffing/SchemaDiffingTest.groovy | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 6a4d6fd081..28a352094e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -150,13 +150,14 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i schemaGraph, GraphQLSchema graphQLSchema) { GraphQLInputType type = inputField.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); -// Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); -// dummyTypeVertex.setBuiltInType(inputFieldVertex.isBuiltInType()); -// schemaGraph.addVertex(dummyTypeVertex); -// schemaGraph.addEdge(new Edge(inputFieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(inputFieldVertex, typeVertex); - typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); + if (inputField.hasSetDefaultValue()) { + typeEdgeLabel += ";defaultValue='" + AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType())) + "'"; + } + + typeEdge.setLabel(typeEdgeLabel); schemaGraph.addEdge(typeEdge); } @@ -227,7 +228,11 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(argumentVertex, typeVertex); - typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); + if (graphQLArgument.hasSetDefaultValue()) { + typeEdgeLabel += ";defaultValue='" + AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType())) + "'"; + } + typeEdge.setLabel(typeEdgeLabel); schemaGraph.addEdge(typeEdge); } @@ -265,9 +270,6 @@ private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGr vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", graphQLArgument.getName()); vertex.add("description", desc(graphQLArgument.getDescription())); - if (graphQLArgument.hasSetDefaultValue()) { - vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType()))); - } createAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph); return vertex; } @@ -393,9 +395,6 @@ private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph sch vertex.setBuiltInType(isIntrospectionNode); vertex.add("name", inputField.getName()); vertex.add("description", desc(inputField.getDescription())); - if (inputField.hasSetDefaultValue()) { - vertex.add("defaultValue", AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType()))); - } createAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph); return vertex; } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 585234468f..a14e4ba507 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1401,6 +1401,38 @@ class SchemaDiffingTest extends Specification { operations.findAll({ it.operation == EditOperation.Operation.CHANGE_EDGE }).size() == 2 } + + def "Recursive input field with default "() { + given: + def schema1 = schema(''' + input I { + name: String + field: I = {name: "default name"} + } + type Query { + foo(arg: I): String + } + ''') + def schema2 = schema(''' + input I { + name: String + field: [I] = [{name: "default name"}] + } + type Query { + foo(arg: I): String + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + // changing the label of the edge to the type + operations.size() == 1 + operations.findAll({ it.operation == EditOperation.Operation.CHANGE_EDGE }).size() == 1 + + } } From 732937147c8468157a96f27c6c9bda851b225f51 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 18:58:05 +1000 Subject: [PATCH 178/294] cleanup --- .../diffing/FillupIsolatedVertices.java | 1 - .../graphql/schema/diffing/SchemaGraph.java | 71 ------------------- 2 files changed, 72 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index 7000605636..c6eb413cf1 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -24,7 +24,6 @@ import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE; import static graphql.schema.diffing.SchemaGraph.ARGUMENT; import static graphql.schema.diffing.SchemaGraph.DIRECTIVE; -import static graphql.schema.diffing.SchemaGraph.DUMMY_TYPE_VERTEX; import static graphql.schema.diffing.SchemaGraph.ENUM; import static graphql.schema.diffing.SchemaGraph.ENUM_VALUE; import static graphql.schema.diffing.SchemaGraph.FIELD; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 2da503ffb1..6889ea3821 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -37,26 +37,8 @@ public class SchemaGraph { public static final String DIRECTIVE = "Directive"; public static final String APPLIED_DIRECTIVE = "AppliedDirective"; public static final String APPLIED_ARGUMENT = "AppliedArgument"; - public static final String DUMMY_TYPE_VERTEX = "__DUMMY_TYPE_VERTEX"; public static final String ISOLATED = "__ISOLATED"; - public static final List ALL_TYPES = Arrays.asList(DUMMY_TYPE_VERTEX, OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM, ENUM_VALUE, APPLIED_DIRECTIVE, FIELD, ARGUMENT, APPLIED_ARGUMENT, DIRECTIVE, INPUT_FIELD); - public static final List ALL_NAMED_TYPES = Arrays.asList(OBJECT, INTERFACE, UNION, INPUT_OBJECT, SCALAR, ENUM); - - /** - * SCHEMA, - * SCALAR, - * OBJECT, - * FIELD_DEFINITION, - * ARGUMENT_DEFINITION, - * INTERFACE, - * UNION, - * ENUM, - * ENUM_VALUE, - * INPUT_OBJECT, - * INPUT_FIELD_DEFINITION - */ - public static final List appliedDirectiveContainerTypes = Arrays.asList(SCALAR, OBJECT, FIELD, ARGUMENT, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD); private List vertices = new ArrayList<>(); private List edges = new ArrayList<>(); @@ -82,19 +64,6 @@ public void addVertex(Vertex vertex) { typeToVertices.put(vertex.getType(), vertex); } - // -// public void removeVertexAndEdges(Vertex vertex) { -// vertices.remove(vertex); -// typeToVertices.remove(vertex.getType(), vertex); -// for (Iterator it = edges.iterator(); it.hasNext(); ) { -// Edge edge = it.next(); -// if (edge.getFrom().equals(vertex) || edge.getTo().equals(vertex)) { -// edgeByVertexPair.remove(edge.getFrom(), edge.getTo()); -// it.remove(); -// } -// } -// } -// public void addVertices(Collection vertices) { for (Vertex vertex : vertices) { this.vertices.add(vertex); @@ -279,41 +248,6 @@ public int getAppliedDirectiveIndex(Vertex appliedDirective) { return Integer.parseInt(adjacentEdges.get(0).getLabel()); } - public Vertex getParentSchemaElement(Vertex vertex) { - switch (vertex.getType()) { - case OBJECT: - break; - case INTERFACE: - break; - case UNION: - break; - case FIELD: - return getFieldsContainerForField(vertex); - case ARGUMENT: - return getFieldOrDirectiveForArgument(vertex); - case SCALAR: - break; - case ENUM: - break; - case ENUM_VALUE: - break; - case INPUT_OBJECT: - break; - case INPUT_FIELD: - return getInputObjectForInputField(vertex); - case DIRECTIVE: - break; - case APPLIED_DIRECTIVE: - break; - case APPLIED_ARGUMENT: - break; - case DUMMY_TYPE_VERTEX: - break; - case ISOLATED: - return Assert.assertShouldNeverHappen(); - } - return assertShouldNeverHappen(); - } public Vertex getEnumForEnumValue(Vertex enumValue) { List adjacentVertices = this.getAdjacentVerticesInverse(enumValue); @@ -321,11 +255,6 @@ public Vertex getEnumForEnumValue(Vertex enumValue) { return adjacentVertices.get(0); } -// public Vertex getFieldOrInputFieldForDummyType(Vertex enumValue) { -// List adjacentVertices = this.getAdjacentVertices(enumValue, vertex -> vertex.getType().equals(FIELD) || vertex.getType().equals(INPUT_FIELD)); -// assertTrue(adjacentVertices.size() == 1, () -> format("No field or input field found for %s", enumValue)); -// return adjacentVertices.get(0); -// } public List getAllAdjacentEdges(List fromList, Vertex to) { List result = new ArrayList<>(); From a6639506d0511cc095b68d4bb5c051c5cf3efbb1 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 19:20:30 +1000 Subject: [PATCH 179/294] fix test --- src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index a14e4ba507..163cf9dcfe 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -27,7 +27,7 @@ class SchemaDiffingTest extends Specification { def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: - schemaGraph.size() == 92 + schemaGraph.size() == 93 } From b3f34cd9e722576b94953159eb876dcae8c4112f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 11 Oct 2022 20:07:11 +1000 Subject: [PATCH 180/294] cleanup --- .../java/graphql/schema/diffing/DiffImpl.java | 2 + .../java/graphql/schema/diffing/Edge.java | 3 + .../graphql/schema/diffing/EditOperation.java | 3 + .../diffing/EditorialCostForMapping.java | 3 + .../diffing/FillupIsolatedVertices.java | 76 ------------------- .../graphql/schema/diffing/GraphPrinter.java | 2 + .../schema/diffing/HungarianAlgorithm.java | 2 + .../java/graphql/schema/diffing/Mapping.java | 2 + .../schema/diffing/SchemaChangedHandler.java | 10 --- .../graphql/schema/diffing/SchemaDiffing.java | 29 +------ .../graphql/schema/diffing/SchemaGraph.java | 2 + .../schema/diffing/SchemaGraphFactory.java | 2 + .../schema/diffing/SortSourceGraph.java | 3 + .../java/graphql/schema/diffing/Util.java | 3 + .../java/graphql/schema/diffing/Vertex.java | 2 + .../ana/EditOperationAnalysisResult.java | 3 + .../diffing/ana/EditOperationAnalyzer.java | 2 + .../schema/diffing/ana/InterfaceModified.java | 3 + .../schema/diffing/ana/ObjectModified.java | 3 + .../schema/diffing/ana/SchemaChange.java | 3 + .../schema/diffing/ana/SchemaChanges.java | 3 + .../schema/diffing/SchemaDiffingTest.groovy | 7 -- 22 files changed, 49 insertions(+), 119 deletions(-) delete mode 100644 src/main/java/graphql/schema/diffing/SchemaChangedHandler.java diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 559733be45..2db9b1d0f1 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -4,6 +4,7 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.util.concurrent.AtomicDoubleArray; +import graphql.Internal; import java.util.ArrayList; import java.util.Arrays; @@ -20,6 +21,7 @@ import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; import static java.util.Collections.singletonList; +@Internal public class DiffImpl { private static MappingEntry LAST_ELEMENT = new MappingEntry(); diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index 14663371bc..b80a733472 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -1,7 +1,10 @@ package graphql.schema.diffing; +import graphql.ExperimentalApi; + import java.util.Objects; +@ExperimentalApi public class Edge { private Vertex from; private Vertex to; diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index cc9150447e..4b5173f8c3 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -1,7 +1,10 @@ package graphql.schema.diffing; +import graphql.ExperimentalApi; + import java.util.Objects; +@ExperimentalApi public class EditOperation { private EditOperation(Operation operation, diff --git a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java index 64cb51b9c2..4137f90cbc 100644 --- a/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java +++ b/src/main/java/graphql/schema/diffing/EditorialCostForMapping.java @@ -1,7 +1,10 @@ package graphql.schema.diffing; +import graphql.Internal; + import java.util.List; +@Internal public class EditorialCostForMapping { /** diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index c6eb413cf1..b08f07cc57 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -53,7 +53,6 @@ public class FillupIsolatedVertices { typeContexts.put(FIELD, fieldContext()); typeContexts.put(ARGUMENT, argumentsContexts()); typeContexts.put(INPUT_FIELD, inputFieldContexts()); -// typeContexts.put(DUMMY_TYPE_VERTEX, dummyTypeContext()); typeContexts.put(OBJECT, objectContext()); typeContexts.put(INTERFACE, interfaceContext()); typeContexts.put(UNION, unionContext()); @@ -106,52 +105,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } -// private static List dummyTypeContext() { -// -// VertexContextSegment dummyType = new VertexContextSegment() { -// @Override -// public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) { -// return vertex.getType(); -// } -// -// @Override -// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { -// return DUMMY_TYPE_VERTEX.equals(vertex.getType()); -// } -// }; -// VertexContextSegment inputObjectOrFieldContainerContext = new VertexContextSegment() { -// @Override -// public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { -// Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); -// if (fieldOrInputField.getType().equals(FIELD)) { -// Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrInputField); -// return fieldsContainer.getType() + "." + fieldsContainer.getName(); -// } else { -// return schemaGraph.getInputObjectForInputField(fieldOrInputField).getName(); -// } -// } -// -// @Override -// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { -// return true; -// } -// }; -// VertexContextSegment inputFieldOrFieldName = new VertexContextSegment() { -// @Override -// public String idForVertex(Vertex dummyType, SchemaGraph schemaGraph) { -// Vertex fieldOrInputField = schemaGraph.getFieldOrInputFieldForDummyType(dummyType); -// return fieldOrInputField.getName(); -// } -// -// @Override -// public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { -// return true; -// } -// }; -// -// List contexts = Arrays.asList(dummyType, inputObjectOrFieldContainerContext, inputFieldOrFieldName); -// return contexts; -// } private static List scalarContext() { VertexContextSegment scalar = new VertexContextSegment() { @@ -778,28 +731,11 @@ public void ensureGraphAreSameSize() { calcPossibleMappings(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT); calcPossibleMappings(typeContexts.get(DIRECTIVE), DIRECTIVE); -// for (Vertex sourceVertex : toRemove.keySet()) { -// sourceGraph.removeVertexAndEdges(sourceVertex); -// Vertex targetVertex = toRemove.get(sourceVertex); -// targetGraph.removeVertexAndEdges(targetVertex); -// } - sourceGraph.addVertices(isolatedVertices.allIsolatedSource); targetGraph.addVertices(isolatedVertices.allIsolatedTarget); Assert.assertTrue(sourceGraph.size() == targetGraph.size()); -// for (Vertex vertex : isolatedVertices.possibleMappings.keySet()) { -// Collection vertices = isolatedVertices.possibleMappings.get(vertex); -// if (vertices.size() > 1) { -// System.out.println("multiple for " + vertex); -// } -// } -// if (sourceGraph.size() < targetGraph.size()) { -// isolatedVertices.isolatedBuiltInSourceVertices.addAll(sourceGraph.addIsolatedVertices(targetGraph.size() - sourceGraph.size(), "source-isolated-builtin-")); -// } else if (sourceGraph.size() > targetGraph.size()) { -// isolatedVertices.isolatedBuiltInTargetVertices.addAll(targetGraph.addIsolatedVertices(sourceGraph.size() - targetGraph.size(), "target-isolated-builtin-")); -// } } @@ -826,9 +762,6 @@ public VertexContextSegment(VertexContextSegment child) { public class IsolatedVertices { -// public Multimap contextToIsolatedSourceVertices = HashMultimap.create(); -// public Multimap contextToIsolatedTargetVertices = HashMultimap.create(); - public Set allIsolatedSource = new LinkedHashSet<>(); public Set allIsolatedTarget = new LinkedHashSet<>(); @@ -851,15 +784,6 @@ public void addIsolatedTarget(Collection isolatedTarget) { allIsolatedTarget.addAll(isolatedTarget); } - // public void putSource(Object contextId, Collection isolatedSourcedVertices) { -// contextToIsolatedSourceVertices.putAll(contextId, isolatedSourcedVertices); -// allIsolatedSource.addAll(isolatedSourcedVertices); -// } -// -// public void putTarget(Object contextId, Collection isolatedTargetVertices) { -// contextToIsolatedTargetVertices.putAll(contextId, isolatedTargetVertices); -// allIsolatedTarget.addAll(isolatedTargetVertices); -// } // public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { return possibleMappings.containsEntry(sourceVertex, targetVertex); diff --git a/src/main/java/graphql/schema/diffing/GraphPrinter.java b/src/main/java/graphql/schema/diffing/GraphPrinter.java index 58d7baa6ba..da713fa22a 100644 --- a/src/main/java/graphql/schema/diffing/GraphPrinter.java +++ b/src/main/java/graphql/schema/diffing/GraphPrinter.java @@ -1,7 +1,9 @@ package graphql.schema.diffing; +import graphql.Internal; import graphql.schema.diffing.dot.Dotfile; +@Internal public class GraphPrinter { public static String print(SchemaGraph schemaGraph) { diff --git a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java index ed53a437cf..6476352792 100644 --- a/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java +++ b/src/main/java/graphql/schema/diffing/HungarianAlgorithm.java @@ -1,6 +1,7 @@ package graphql.schema.diffing; import com.google.common.util.concurrent.AtomicDoubleArray; +import graphql.Internal; import java.util.Arrays; @@ -49,6 +50,7 @@ * * @author Kevin L. Stern */ +@Internal public class HungarianAlgorithm { // changed by reduce public final AtomicDoubleArray[] costMatrix; diff --git a/src/main/java/graphql/schema/diffing/Mapping.java b/src/main/java/graphql/schema/diffing/Mapping.java index e7e000ae2f..42ec164f03 100644 --- a/src/main/java/graphql/schema/diffing/Mapping.java +++ b/src/main/java/graphql/schema/diffing/Mapping.java @@ -2,11 +2,13 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import graphql.Internal; import java.util.ArrayList; import java.util.List; import java.util.Objects; +@Internal public class Mapping { private BiMap map = HashBiMap.create(); private List sourceList = new ArrayList<>(); diff --git a/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java b/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java deleted file mode 100644 index 2f1a7807bc..0000000000 --- a/src/main/java/graphql/schema/diffing/SchemaChangedHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package graphql.schema.diffing; - -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLType; - -public interface SchemaChangedHandler { - - void fieldRemoved(String description); - -} diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index ec5411c5ad..fa24beccda 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,5 +1,6 @@ package graphql.schema.diffing; +import graphql.ExperimentalApi; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.ana.EditOperationAnalysisResult; import graphql.schema.diffing.ana.EditOperationAnalyzer; @@ -12,9 +13,11 @@ import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; import static java.util.Collections.singletonList; +@ExperimentalApi public class SchemaDiffing { + SchemaGraph sourceGraph; SchemaGraph targetGraph; @@ -140,30 +143,4 @@ private List calcEdgeOperations(Mapping mapping) { } -// List debugMap = getDebugMap(bestFullMapping.get()); -// for (String debugLine : debugMap) { -// System.out.println(debugLine); -// } -// System.out.println("edit : " + bestEdit); -// for (EditOperation editOperation : bestEdit.get()) { -// System.out.println(editOperation); -// } -// private List diffImplImpl() { - -// private void logUnmappable(AtomicDoubleArray[] costMatrix, int[] assignments, List sourceList, ArrayList availableTargetVertices, int level) { -// for (int i = 0; i < assignments.length; i++) { -// double value = costMatrix[i].get(assignments[i]); -// if (value >= Integer.MAX_VALUE) { -// System.out.println("i " + i + " can't mapped"); -// Vertex v = sourceList.get(i + level - 1); -// Vertex u = availableTargetVertices.get(assignments[i]); -// System.out.println("from " + v + " to " + u); -// } -// } -// } -// -// -// // minimum number of edit operations for a full mapping -// - } diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index 6889ea3821..d5ff50f310 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -6,6 +6,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Table; import graphql.Assert; +import graphql.ExperimentalApi; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -21,6 +22,7 @@ import static graphql.Assert.assertTrue; import static java.lang.String.format; +@ExperimentalApi public class SchemaGraph { public static final String SCHEMA = "Schema"; diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 8d4b1712ff..caead1493a 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -1,6 +1,7 @@ package graphql.schema.diffing; import graphql.GraphQLContext; +import graphql.Internal; import graphql.execution.ValuesResolver; import graphql.introspection.Introspection; import graphql.language.AstPrinter; @@ -22,6 +23,7 @@ import static graphql.Assert.assertNotNull; +@Internal public class SchemaGraphFactory { private int counter = 1; diff --git a/src/main/java/graphql/schema/diffing/SortSourceGraph.java b/src/main/java/graphql/schema/diffing/SortSourceGraph.java index 696b5558be..d9169e167d 100644 --- a/src/main/java/graphql/schema/diffing/SortSourceGraph.java +++ b/src/main/java/graphql/schema/diffing/SortSourceGraph.java @@ -1,5 +1,7 @@ package graphql.schema.diffing; +import graphql.Internal; + import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; @@ -7,6 +9,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +@Internal public class SortSourceGraph { public static void sortSourceGraph(SchemaGraph sourceGraph, SchemaGraph targetGraph, FillupIsolatedVertices.IsolatedVertices isolatedVertices) { diff --git a/src/main/java/graphql/schema/diffing/Util.java b/src/main/java/graphql/schema/diffing/Util.java index 5ef5d29f3f..38bd0878c1 100644 --- a/src/main/java/graphql/schema/diffing/Util.java +++ b/src/main/java/graphql/schema/diffing/Util.java @@ -1,8 +1,11 @@ package graphql.schema.diffing; +import graphql.Internal; + import java.util.List; import java.util.Set; +@Internal public class Util { public static void diffNamedList(Set sourceNames, Set targetNames, diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 5807137d80..5088bcee6b 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -1,6 +1,7 @@ package graphql.schema.diffing; import graphql.Assert; +import graphql.ExperimentalApi; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -9,6 +10,7 @@ import java.util.Properties; import java.util.Set; +@ExperimentalApi public class Vertex { private String type; diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java index 7a68baf367..826de33737 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -1,7 +1,10 @@ package graphql.schema.diffing.ana; +import graphql.ExperimentalApi; + import java.util.Map; +@ExperimentalApi public class EditOperationAnalysisResult { private final Map objectChanges; private final Map interfaceChanges; diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 65d7719100..b52193c523 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -1,6 +1,7 @@ package graphql.schema.diffing.ana; import graphql.Assert; +import graphql.ExperimentalApi; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.Edge; import graphql.schema.diffing.EditOperation; @@ -19,6 +20,7 @@ /** * Higher level GraphQL semantic assigned to */ +@ExperimentalApi public class EditOperationAnalyzer { private GraphQLSchema oldSchema; diff --git a/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java b/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java index 963c6327b7..77b164221e 100644 --- a/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java +++ b/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java @@ -1,8 +1,11 @@ package graphql.schema.diffing.ana; +import graphql.ExperimentalApi; + import java.util.ArrayList; import java.util.List; +@ExperimentalApi public class InterfaceModified implements SchemaChange.InterfaceChange { private final String name; diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java index eaf7cc70df..8bf9c9ddfd 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -1,8 +1,11 @@ package graphql.schema.diffing.ana; +import graphql.ExperimentalApi; + import java.util.ArrayList; import java.util.List; +@ExperimentalApi public class ObjectModified implements SchemaChange.ObjectChange { private final String name; diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java index c93374d520..329a37b264 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java @@ -1,5 +1,8 @@ package graphql.schema.diffing.ana; +import graphql.ExperimentalApi; + +@ExperimentalApi public interface SchemaChange { interface ObjectChange extends SchemaChange { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java index b36cf4064e..83b3fc861f 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java @@ -1,5 +1,8 @@ package graphql.schema.diffing.ana; +import graphql.ExperimentalApi; + +@ExperimentalApi public class SchemaChanges { /** diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 163cf9dcfe..de36041e7c 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -528,13 +528,6 @@ class SchemaDiffingTest extends Specification { } """) - def changeHandler = new SchemaChangedHandler() { - @Override - void fieldRemoved(String description) { - println "field removed: " + description - } - } - def diffing = new SchemaDiffing() when: def diff = diffing.diffGraphQLSchema(schema1, schema2) From 432e73474508bb1d6d22eb0775f1baf4f0b915ec Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Fri, 14 Oct 2022 10:09:00 +1100 Subject: [PATCH 181/294] Adding Locale to Parser (#2921) * Adding Locale to Parser * Adding Locale to Parser - merged in master * Merged master in parser branch --- src/main/java/graphql/ParseAndValidate.java | 8 +- src/main/java/graphql/i18n/I18n.java | 3 +- .../graphql/language/PrettyAstPrinter.java | 5 +- .../graphql/parser/ExtendedBailStrategy.java | 37 ++++- .../parser/GraphqlAntlrToLanguage.java | 43 +----- .../parser/InvalidSyntaxException.java | 22 +-- .../parser/NodeToRuleCapturingParser.java | 4 +- .../parser/ParseCancelledException.java | 12 -- src/main/java/graphql/parser/Parser.java | 146 ++++++++++-------- .../graphql/parser/ParserEnvironment.java | 14 +- .../graphql/parser/StringValueParsing.java | 9 +- src/main/java/graphql/parser/UnicodeUtil.java | 24 +-- .../InvalidUnicodeSyntaxException.java | 16 ++ .../exceptions/MoreTokensSyntaxException.java | 17 ++ .../exceptions/ParseCancelledException.java | 18 +++ .../java/graphql/schema/idl/SchemaParser.java | 6 +- src/main/java/graphql/util/Anonymizer.java | 5 +- src/main/resources/i18n/Parsing.properties | 26 ++++ src/test/groovy/graphql/GraphQLTest.groovy | 2 +- .../graphql/ParseAndValidateTest.groovy | 2 +- .../groovy/graphql/parser/ParserTest.groovy | 23 +-- .../parser/StringValueParsingTest.groovy | 15 +- .../StringValueParsingUnicodeTest.groovy | 76 ++++----- 23 files changed, 312 insertions(+), 221 deletions(-) delete mode 100644 src/main/java/graphql/parser/ParseCancelledException.java create mode 100644 src/main/java/graphql/parser/exceptions/InvalidUnicodeSyntaxException.java create mode 100644 src/main/java/graphql/parser/exceptions/MoreTokensSyntaxException.java create mode 100644 src/main/java/graphql/parser/exceptions/ParseCancelledException.java create mode 100644 src/main/resources/i18n/Parsing.properties diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index 63c29edad2..9410a5ec1b 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -3,6 +3,7 @@ import graphql.language.Document; import graphql.parser.InvalidSyntaxException; import graphql.parser.Parser; +import graphql.parser.ParserEnvironment; import graphql.parser.ParserOptions; import graphql.schema.GraphQLSchema; import graphql.validation.ValidationError; @@ -65,7 +66,12 @@ public static ParseAndValidateResult parse(@NotNull ExecutionInput executionInpu // we use the query parser options by default if they are not specified parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultOperationParserOptions()); Parser parser = new Parser(); - Document document = parser.parseDocument(executionInput.getQuery(), parserOptions); + Locale locale = executionInput.getLocale() == null ? Locale.getDefault() : executionInput.getLocale(); + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() + .document(executionInput.getQuery()).parserOptions(parserOptions) + .locale(locale) + .build(); + Document document = parser.parseDocument(parserEnvironment); return ParseAndValidateResult.newResult().document(document).variables(executionInput.getVariables()).build(); } catch (InvalidSyntaxException e) { return ParseAndValidateResult.newResult().syntaxException(e).variables(executionInput.getVariables()).build(); diff --git a/src/main/java/graphql/i18n/I18n.java b/src/main/java/graphql/i18n/I18n.java index bf9b5aafc3..7e29de75b4 100644 --- a/src/main/java/graphql/i18n/I18n.java +++ b/src/main/java/graphql/i18n/I18n.java @@ -21,6 +21,7 @@ public class I18n { * This enum is a type safe way to control what resource bundle to load from */ public enum BundleType { + Parsing, Scalars, Validation, Execution, @@ -38,9 +39,9 @@ public enum BundleType { @VisibleForTesting protected I18n(BundleType bundleType, Locale locale) { - this.locale = locale; assertNotNull(bundleType); assertNotNull(locale); + this.locale = locale; this.resourceBundle = ResourceBundle.getBundle(bundleType.baseName, locale); } diff --git a/src/main/java/graphql/language/PrettyAstPrinter.java b/src/main/java/graphql/language/PrettyAstPrinter.java index 2ff364ec77..eb02039f29 100644 --- a/src/main/java/graphql/language/PrettyAstPrinter.java +++ b/src/main/java/graphql/language/PrettyAstPrinter.java @@ -5,6 +5,7 @@ import graphql.collect.ImmutableKit; import graphql.parser.CommentParser; import graphql.parser.NodeToRuleCapturingParser; +import graphql.parser.ParserEnvironment; import java.util.Collections; import java.util.List; @@ -14,6 +15,7 @@ import java.util.stream.Collectors; import static graphql.Assert.assertTrue; +import static graphql.parser.ParserEnvironment.newParserEnvironment; /** * A printer that acts as a code formatter. @@ -67,7 +69,8 @@ public String print(Node node) { public static String print(String schemaDefinition, PrettyPrinterOptions options) { NodeToRuleCapturingParser parser = new NodeToRuleCapturingParser(); - Document document = parser.parseDocument(schemaDefinition); + ParserEnvironment parserEnvironment = newParserEnvironment().document(schemaDefinition).build(); + Document document = parser.parseDocument(parserEnvironment); return new PrettyAstPrinter(parser.getParserContext(), options).print(document); } diff --git a/src/main/java/graphql/parser/ExtendedBailStrategy.java b/src/main/java/graphql/parser/ExtendedBailStrategy.java index e6d33d5ea9..a0861ed0c1 100644 --- a/src/main/java/graphql/parser/ExtendedBailStrategy.java +++ b/src/main/java/graphql/parser/ExtendedBailStrategy.java @@ -1,19 +1,25 @@ package graphql.parser; +import com.google.common.collect.ImmutableList; import graphql.Internal; import graphql.language.SourceLocation; +import graphql.parser.exceptions.MoreTokensSyntaxException; import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.ParseCancellationException; +import java.util.List; + @Internal public class ExtendedBailStrategy extends BailErrorStrategy { private final MultiSourceReader multiSourceReader; + private final ParserEnvironment environment; - public ExtendedBailStrategy(MultiSourceReader multiSourceReader) { + public ExtendedBailStrategy(MultiSourceReader multiSourceReader, ParserEnvironment environment) { this.multiSourceReader = multiSourceReader; + this.environment = environment; } @Override @@ -37,23 +43,38 @@ public Token recoverInline(Parser recognizer) throws RecognitionException { InvalidSyntaxException mkMoreTokensException(Token token) { SourceLocation sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, token); String sourcePreview = AntlrHelper.createPreview(multiSourceReader, token.getLine()); - return new InvalidSyntaxException(sourceLocation, - "There are more tokens in the query that have not been consumed", - sourcePreview, token.getText(), null); + return new MoreTokensSyntaxException(environment.getI18N(), sourceLocation, + token.getText(), sourcePreview); } private InvalidSyntaxException mkException(Parser recognizer, RecognitionException cause) { - String sourcePreview = null; - String offendingToken = null; - SourceLocation sourceLocation = null; + String sourcePreview; + String offendingToken; + final SourceLocation sourceLocation; Token currentToken = recognizer.getCurrentToken(); if (currentToken != null) { sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, currentToken); offendingToken = currentToken.getText(); sourcePreview = AntlrHelper.createPreview(multiSourceReader, currentToken.getLine()); + } else { + sourcePreview = null; + offendingToken = null; + sourceLocation = null; + } + + String msgKey; + List args; + SourceLocation location = sourceLocation == null ? SourceLocation.EMPTY : sourceLocation; + if (offendingToken == null) { + msgKey = "InvalidSyntaxBail.noToken"; + args = ImmutableList.of(location.getLine(), location.getColumn()); + } else { + msgKey = "InvalidSyntaxBail.full"; + args = ImmutableList.of(offendingToken, sourceLocation.getLine(), sourceLocation.getColumn()); } - return new InvalidSyntaxException(sourceLocation, null, sourcePreview, offendingToken, cause); + String msg = environment.getI18N().msg(msgKey, args); + return new InvalidSyntaxException(msg, sourceLocation, offendingToken, sourcePreview, cause); } } diff --git a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java index 5539234806..eb5070bc66 100644 --- a/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java +++ b/src/main/java/graphql/parser/GraphqlAntlrToLanguage.java @@ -5,6 +5,7 @@ import graphql.Assert; import graphql.Internal; import graphql.collect.ImmutableKit; +import graphql.i18n.I18n; import graphql.language.Argument; import graphql.language.ArrayValue; import graphql.language.BooleanValue; @@ -91,44 +92,16 @@ public class GraphqlAntlrToLanguage { private final CommonTokenStream tokens; private final MultiSourceReader multiSourceReader; private final ParserOptions parserOptions; - private final Map, ParserRuleContext> nodeToRuleMap; + private final I18n i18N; - /** - * @param tokens the token stream - * @param multiSourceReader the source of the query document - */ - public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { - this(tokens, multiSourceReader, null); - } - - /** - * @param tokens the token stream - * @param multiSourceReader the source of the query document - * @param parserOptions the parser options - */ - public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { - this(tokens, multiSourceReader, parserOptions, null); - } - - /** - * @param tokens the token stream - * @param multiSourceReader the source of the query document - * @param parserOptions the parser options - * @param nodeToRuleMap a map that will be used to accumulate the ParserRuleContext associated with each node. - * This information can be used after the parsing process is done to access some elements - * that are usually lost during parsing. If the map is "null", no accumulation will be performed. - */ - public GraphqlAntlrToLanguage( - CommonTokenStream tokens, - MultiSourceReader multiSourceReader, - ParserOptions parserOptions, - @Nullable Map, ParserRuleContext> nodeToRuleMap - ) { + public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions, I18n i18N, @Nullable Map, ParserRuleContext> nodeToRuleMap) { this.tokens = tokens; this.multiSourceReader = multiSourceReader; this.parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultParserOptions()); + this.i18N = i18N; this.nodeToRuleMap = nodeToRuleMap; + } public ParserOptions getParserOptions() { @@ -244,7 +217,7 @@ protected SelectionSet createSelectionSet(GraphqlParser.SelectionSetContext ctx) if (selectionContext.inlineFragment() != null) { return createInlineFragment(selectionContext.inlineFragment()); } - return (Selection) Assert.assertShouldNeverHappen(); + return Assert.assertShouldNeverHappen(); }); builder.selections(selections); @@ -803,7 +776,7 @@ protected String quotedString(TerminalNode terminalNode) { if (multiLine) { return parseTripleQuotedString(strText); } else { - return parseSingleQuotedString(strText, sourceLocation); + return parseSingleQuotedString(i18N, strText, sourceLocation); } } @@ -880,7 +853,7 @@ protected Description newDescription(GraphqlParser.DescriptionContext descriptio if (multiLine) { content = parseTripleQuotedString(content); } else { - content = parseSingleQuotedString(content, sourceLocation); + content = parseSingleQuotedString(i18N, content, sourceLocation); } return new Description(content, sourceLocation, multiLine); } diff --git a/src/main/java/graphql/parser/InvalidSyntaxException.java b/src/main/java/graphql/parser/InvalidSyntaxException.java index 9136512c6f..939dfd2e2d 100644 --- a/src/main/java/graphql/parser/InvalidSyntaxException.java +++ b/src/main/java/graphql/parser/InvalidSyntaxException.java @@ -2,6 +2,7 @@ import graphql.GraphQLException; +import graphql.Internal; import graphql.InvalidSyntaxError; import graphql.PublicApi; import graphql.language.SourceLocation; @@ -20,35 +21,20 @@ public class InvalidSyntaxException extends GraphQLException { private final String offendingToken; private final SourceLocation location; - InvalidSyntaxException(SourceLocation location, String msg, String sourcePreview, String offendingToken, Exception cause) { + @Internal + protected InvalidSyntaxException(String msg, SourceLocation location, String offendingToken, String sourcePreview, Exception cause) { super(cause); - this.message = mkMessage(msg, offendingToken, location); + this.message = msg; this.sourcePreview = sourcePreview; this.offendingToken = offendingToken; this.location = location; } - private String mkMessage(String msg, String offendingToken, SourceLocation location) { - StringBuilder sb = new StringBuilder(); - sb.append("Invalid Syntax :"); - if (msg != null) { - sb.append(" ").append(msg); - } - if (offendingToken != null) { - sb.append(String.format(" offending token '%s'", offendingToken)); - } - if (location != null) { - sb.append(String.format(" at line %d column %d", location.getLine(), location.getColumn())); - } - return sb.toString(); - } - public InvalidSyntaxError toInvalidSyntaxError() { List sourceLocations = location == null ? null : Collections.singletonList(location); return new InvalidSyntaxError(sourceLocations, message, sourcePreview, offendingToken); } - @Override public String getMessage() { return message; diff --git a/src/main/java/graphql/parser/NodeToRuleCapturingParser.java b/src/main/java/graphql/parser/NodeToRuleCapturingParser.java index 88ffc94be6..f63fe58166 100644 --- a/src/main/java/graphql/parser/NodeToRuleCapturingParser.java +++ b/src/main/java/graphql/parser/NodeToRuleCapturingParser.java @@ -21,9 +21,9 @@ public NodeToRuleCapturingParser() { } @Override - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserEnvironment environment) { parserContext.tokens = tokens; - return new GraphqlAntlrToLanguage(tokens, multiSourceReader, parserOptions, parserContext.nodeToRuleMap); + return new GraphqlAntlrToLanguage(tokens, multiSourceReader, environment.getParserOptions(), environment.getI18N(), parserContext.nodeToRuleMap); } public ParserContext getParserContext() { diff --git a/src/main/java/graphql/parser/ParseCancelledException.java b/src/main/java/graphql/parser/ParseCancelledException.java deleted file mode 100644 index c416c12504..0000000000 --- a/src/main/java/graphql/parser/ParseCancelledException.java +++ /dev/null @@ -1,12 +0,0 @@ -package graphql.parser; - -import graphql.PublicApi; -import graphql.language.SourceLocation; - -@PublicApi -public class ParseCancelledException extends InvalidSyntaxException { - - public ParseCancelledException(String msg, SourceLocation sourceLocation, String offendingToken) { - super(sourceLocation, msg, null, offendingToken, null); - } -} diff --git a/src/main/java/graphql/parser/Parser.java b/src/main/java/graphql/parser/Parser.java index bc6fd8e3b4..c1aac322f3 100644 --- a/src/main/java/graphql/parser/Parser.java +++ b/src/main/java/graphql/parser/Parser.java @@ -1,5 +1,6 @@ package graphql.parser; +import com.google.common.collect.ImmutableList; import graphql.DeprecatedAt; import graphql.Internal; import graphql.PublicApi; @@ -11,6 +12,7 @@ import graphql.parser.antlr.GraphqlBaseListener; import graphql.parser.antlr.GraphqlLexer; import graphql.parser.antlr.GraphqlParser; +import graphql.parser.exceptions.ParseCancelledException; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CodePointCharStream; @@ -37,10 +39,10 @@ *

* You should not generally need to call this class as the {@link graphql.GraphQL} code sets this up for you * but if you are doing specific graphql utilities this class is essential. - * + *

* Graphql syntax has a series of characters, such as spaces, new lines and commas that are not considered relevant * to the syntax. However they can be captured and associated with the AST elements they belong to. - * + *

* This costs more memory but for certain use cases (like editors) this maybe be useful. We have chosen to no capture * ignored characters by default but you can turn this on, either per parse or statically for the whole JVM * via {@link ParserOptions#setDefaultParserOptions(ParserOptions)} ()}} @@ -55,6 +57,19 @@ public class Parser { @Internal public static final int CHANNEL_WHITESPACE = 3; + /** + * Parses a string input into a graphql AST {@link Document} + * + * @param environment the parser environment to use + * + * @return an AST {@link Document} + * + * @throws InvalidSyntaxException if the document is not valid graphql syntax + */ + public static Document parse(ParserEnvironment environment) throws InvalidSyntaxException { + return new Parser().parseDocument(environment); + } + /** * Parses a string input into a graphql AST {@link Document} * @@ -68,9 +83,6 @@ public static Document parse(String input) throws InvalidSyntaxException { return new Parser().parseDocument(input); } - public static Document parse(ParserEnvironment environment) throws InvalidSyntaxException { - return new Parser().parseDocument(environment); - } /** * Parses a string input into a graphql AST {@link Value} @@ -98,6 +110,19 @@ public static Type parseType(String input) throws InvalidSyntaxException { return new Parser().parseTypeImpl(input); } + /** + * Parses document text into a graphql AST {@link Document} + * + * @param environment the parser environment to sue + * + * @return an AST {@link Document} + * + * @throws InvalidSyntaxException if the input is not valid graphql syntax + */ + public Document parseDocument(ParserEnvironment environment) throws InvalidSyntaxException { + return parseDocumentImpl(environment); + } + /** * Parses a string input into a graphql AST {@link Document} * @@ -111,6 +136,22 @@ public Document parseDocument(String input) throws InvalidSyntaxException { return parseDocument(input, (ParserOptions) null); } + /** + * Parses reader input into a graphql AST {@link Document} + * + * @param reader the reader input to parse + * + * @return an AST {@link Document} + * + * @throws InvalidSyntaxException if the input is not valid graphql syntax + */ + public Document parseDocument(Reader reader) throws InvalidSyntaxException { + ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() + .document(reader) + .build(); + return parseDocumentImpl(parserEnvironment); + } + /** * Parses a string input into a graphql AST {@link Document} * @@ -120,7 +161,10 @@ public Document parseDocument(String input) throws InvalidSyntaxException { * @return an AST {@link Document} * * @throws InvalidSyntaxException if the input is not valid graphql syntax + * @deprecated use {#{@link #parse(ParserEnvironment)}} instead */ + @DeprecatedAt("2022-08-31") + @Deprecated public Document parseDocument(String input, String sourceName) throws InvalidSyntaxException { MultiSourceReader multiSourceReader = MultiSourceReader.newMultiSourceReader() .string(input, sourceName) @@ -138,7 +182,10 @@ public Document parseDocument(String input, String sourceName) throws InvalidSyn * @return an AST {@link Document} * * @throws InvalidSyntaxException if the input is not valid graphql syntax + * @deprecated use {#{@link #parse(ParserEnvironment)}} instead */ + @DeprecatedAt("2022-08-31") + @Deprecated public Document parseDocument(String input, ParserOptions parserOptions) throws InvalidSyntaxException { MultiSourceReader multiSourceReader = MultiSourceReader.newMultiSourceReader() .string(input, null) @@ -147,22 +194,6 @@ public Document parseDocument(String input, ParserOptions parserOptions) throws return parseDocument(multiSourceReader, parserOptions); } - /** - * Parses reader input into a graphql AST {@link Document} - * - * @param reader the reader input to parse - * - * @return an AST {@link Document} - * - * @throws InvalidSyntaxException if the input is not valid graphql syntax - */ - public Document parseDocument(Reader reader) throws InvalidSyntaxException { - ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() - .document(reader) - .build(); - return parseDocumentImpl(parserEnvironment); - } - /** * Parses reader input into a graphql AST {@link Document} * @@ -172,7 +203,10 @@ public Document parseDocument(Reader reader) throws InvalidSyntaxException { * @return an AST {@link Document} * * @throws InvalidSyntaxException if the input is not valid graphql syntax + * @deprecated use {#{@link #parse(ParserEnvironment)}} instead */ + @DeprecatedAt("2022-08-31") + @Deprecated public Document parseDocument(Reader reader, ParserOptions parserOptions) throws InvalidSyntaxException { ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() .document(reader) @@ -181,20 +215,6 @@ public Document parseDocument(Reader reader, ParserOptions parserOptions) throws return parseDocumentImpl(parserEnvironment); } - /** - * Parses document text into a graphql AST {@link Document} - * - * @param environment the parser environment to sue - * - * @return an AST {@link Document} - * - * @throws InvalidSyntaxException if the input is not valid graphql syntax - */ - public Document parseDocument(ParserEnvironment environment) throws InvalidSyntaxException { - return parseDocumentImpl(environment); - } - - private Document parseDocumentImpl(ParserEnvironment environment) throws InvalidSyntaxException { BiFunction nodeFunction = (parser, toLanguage) -> { GraphqlParser.DocumentContext documentContext = parser.document(); @@ -253,10 +273,20 @@ private Node parseImpl(ParserEnvironment environment, BiFunction recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String antlerMsg, RecognitionException e) { SourceLocation sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, line, charPositionInLine); String preview = AntlrHelper.createPreview(multiSourceReader, line); - throw new InvalidSyntaxException(sourceLocation, msg, preview, null, null); + String msgKey; + List args; + if (antlerMsg == null) { + msgKey = "InvalidSyntax.noMessage"; + args = ImmutableList.of(sourceLocation.getLine(), sourceLocation.getColumn()); + } else { + msgKey = "InvalidSyntax.full"; + args = ImmutableList.of(antlerMsg, sourceLocation.getLine(), sourceLocation.getColumn()); + } + String msg = environment.getI18N().msg(msgKey, args); + throw new InvalidSyntaxException(msg, sourceLocation, null, preview, null); } }); @@ -267,7 +297,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int // this lexer wrapper allows us to stop lexing when too many tokens are in place. This prevents DOS attacks. int maxTokens = parserOptions.getMaxTokens(); int maxWhitespaceTokens = parserOptions.getMaxWhitespaceTokens(); - BiConsumer onTooManyTokens = (maxTokenCount, token) -> throwCancelParseIfTooManyTokens(token, maxTokenCount, multiSourceReader); + BiConsumer onTooManyTokens = (maxTokenCount, token) -> throwCancelParseIfTooManyTokens(environment, token, maxTokenCount, multiSourceReader); SafeTokenSource safeTokenSource = new SafeTokenSource(lexer, maxTokens, maxWhitespaceTokens, onTooManyTokens); CommonTokenStream tokens = new CommonTokenStream(safeTokenSource); @@ -276,16 +306,13 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int parser.removeErrorListeners(); parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - ExtendedBailStrategy bailStrategy = new ExtendedBailStrategy(multiSourceReader); + ExtendedBailStrategy bailStrategy = new ExtendedBailStrategy(multiSourceReader, environment); parser.setErrorHandler(bailStrategy); // preserve old protected call semantics - remove at some point - GraphqlAntlrToLanguage toLanguage = getAntlrToLanguage(tokens, multiSourceReader); - if (toLanguage == null) { - toLanguage = getAntlrToLanguage(tokens, multiSourceReader, parserOptions); - } + GraphqlAntlrToLanguage toLanguage = getAntlrToLanguage(tokens, multiSourceReader, environment); - setupParserListener(multiSourceReader, parser, toLanguage); + setupParserListener(environment, multiSourceReader, parser, toLanguage); // @@ -312,7 +339,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int return node; } - private void setupParserListener(MultiSourceReader multiSourceReader, GraphqlParser parser, GraphqlAntlrToLanguage toLanguage) { + private void setupParserListener(ParserEnvironment environment, MultiSourceReader multiSourceReader, GraphqlParser parser, GraphqlAntlrToLanguage toLanguage) { ParserOptions parserOptions = toLanguage.getParserOptions(); ParsingListener parsingListener = parserOptions.getParsingListener(); int maxTokens = parserOptions.getMaxTokens(); @@ -343,14 +370,14 @@ public int getCharPositionInLine() { count++; if (count > maxTokens) { - throwCancelParseIfTooManyTokens(token, maxTokens, multiSourceReader); + throwCancelParseIfTooManyTokens(environment, token, maxTokens, multiSourceReader); } } }; parser.addParseListener(listener); } - private void throwCancelParseIfTooManyTokens(Token token, int maxTokens, MultiSourceReader multiSourceReader) throws ParseCancelledException { + private void throwCancelParseIfTooManyTokens(ParserEnvironment environment, Token token, int maxTokens, MultiSourceReader multiSourceReader) throws ParseCancelledException { String tokenType = "grammar"; SourceLocation sourceLocation = null; String offendingToken = null; @@ -361,24 +388,7 @@ private void throwCancelParseIfTooManyTokens(Token token, int maxTokens, MultiSo offendingToken = token.getText(); sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, token.getLine(), token.getCharPositionInLine()); } - String msg = String.format("More than %d %s tokens have been presented. To prevent Denial Of Service attacks, parsing has been cancelled.", maxTokens, tokenType); - throw new ParseCancelledException(msg, sourceLocation, offendingToken); - } - - /** - * Allows you to override the ANTLR to AST code. - * - * @param tokens the token stream - * @param multiSourceReader the source of the query document - * - * @return a new GraphqlAntlrToLanguage instance - * - * @deprecated - really should use {@link #getAntlrToLanguage(CommonTokenStream, MultiSourceReader, ParserOptions)} - */ - @Deprecated - @DeprecatedAt("2021-06-23") - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader) { - return null; + throw new ParseCancelledException(environment.getI18N(), sourceLocation, offendingToken, maxTokens, tokenType); } /** @@ -386,11 +396,11 @@ protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, Mu * * @param tokens the token stream * @param multiSourceReader the source of the query document - * @param parserOptions - the parser options + * @param environment the parser environment * * @return a new GraphqlAntlrToLanguage instance */ - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { - return new GraphqlAntlrToLanguage(tokens, multiSourceReader, parserOptions); + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserEnvironment environment) { + return new GraphqlAntlrToLanguage(tokens, multiSourceReader, environment.getParserOptions(), environment.getI18N(), null); } } diff --git a/src/main/java/graphql/parser/ParserEnvironment.java b/src/main/java/graphql/parser/ParserEnvironment.java index 417aa2d3dd..7f0ac696ba 100644 --- a/src/main/java/graphql/parser/ParserEnvironment.java +++ b/src/main/java/graphql/parser/ParserEnvironment.java @@ -1,6 +1,7 @@ package graphql.parser; import graphql.PublicApi; +import graphql.i18n.I18n; import java.io.Reader; import java.io.StringReader; @@ -29,6 +30,11 @@ public interface ParserEnvironment { */ Locale getLocale(); + /** + * @return the {@link I18n} to produce parsing error messages with + */ + I18n getI18N(); + /** * @return a builder of new parsing options */ @@ -39,10 +45,8 @@ static Builder newParserEnvironment() { class Builder { Reader reader; ParserOptions parserOptions = ParserOptions.getDefaultParserOptions(); - Locale locale = Locale.getDefault(); - public Builder() { } @@ -66,6 +70,7 @@ public Builder locale(Locale locale) { } public ParserEnvironment build() { + I18n i18n = I18n.i18n(I18n.BundleType.Parsing, locale); return new ParserEnvironment() { @Override public Reader getDocument() { @@ -81,6 +86,11 @@ public ParserOptions getParserOptions() { public Locale getLocale() { return locale; } + + @Override + public I18n getI18N() { + return i18n; + } }; } } diff --git a/src/main/java/graphql/parser/StringValueParsing.java b/src/main/java/graphql/parser/StringValueParsing.java index d9da00dc83..56b1f1ec88 100644 --- a/src/main/java/graphql/parser/StringValueParsing.java +++ b/src/main/java/graphql/parser/StringValueParsing.java @@ -2,6 +2,7 @@ import graphql.Assert; import graphql.Internal; +import graphql.i18n.I18n; import graphql.language.SourceLocation; import java.io.StringWriter; @@ -103,7 +104,7 @@ private static boolean containsOnlyWhiteSpace(String str) { return leadingWhitespace(str) == str.length(); } - public static String parseSingleQuotedString(String string, SourceLocation sourceLocation) { + public static String parseSingleQuotedString(I18n i18n, String string, SourceLocation sourceLocation) { StringWriter writer = new StringWriter(string.length() - 2); int end = string.length() - 1; for (int i = 1; i < end; i++) { @@ -140,7 +141,7 @@ public static String parseSingleQuotedString(String string, SourceLocation sourc writer.write('\t'); continue; case 'u': - i = UnicodeUtil.parseAndWriteUnicode(writer, string, i, sourceLocation); + i = UnicodeUtil.parseAndWriteUnicode(i18n, writer, string, i, sourceLocation); continue; default: Assert.assertShouldNeverHappen(); @@ -148,8 +149,4 @@ public static String parseSingleQuotedString(String string, SourceLocation sourc } return writer.toString(); } - - public static String parseSingleQuotedString(String string) { - return parseSingleQuotedString(string, null); - } } diff --git a/src/main/java/graphql/parser/UnicodeUtil.java b/src/main/java/graphql/parser/UnicodeUtil.java index cdf55ace47..b32cbfea6a 100644 --- a/src/main/java/graphql/parser/UnicodeUtil.java +++ b/src/main/java/graphql/parser/UnicodeUtil.java @@ -1,7 +1,9 @@ package graphql.parser; import graphql.Internal; +import graphql.i18n.I18n; import graphql.language.SourceLocation; +import graphql.parser.exceptions.InvalidUnicodeSyntaxException; import java.io.IOException; import java.io.StringWriter; @@ -19,14 +21,14 @@ public class UnicodeUtil { public static final int TRAILING_SURROGATE_LOWER_BOUND = 0xDC00; public static final int TRAILING_SURROGATE_UPPER_BOUND = 0xDFFF; - public static int parseAndWriteUnicode(StringWriter writer, String string, int i, SourceLocation sourceLocation) { + public static int parseAndWriteUnicode(I18n i18n, StringWriter writer, String string, int i, SourceLocation sourceLocation) { // Unicode code points can either be: // 1. Unbraced: four hex characters in the form \\u597D, or // 2. Braced: any number of hex characters surrounded by braces in the form \\u{1F37A} // Extract the code point hex digits. Index i points to 'u' int startIndex = isBracedEscape(string, i) ? i + 2 : i + 1; - int endIndexExclusive = getEndIndexExclusive(string, i, sourceLocation); + int endIndexExclusive = getEndIndexExclusive(i18n, string, i, sourceLocation); // Index for parser to continue at, the last character of the escaped unicode character. Either } or hex digit int continueIndex = isBracedEscape(string, i) ? endIndexExclusive : endIndexExclusive - 1; @@ -34,16 +36,16 @@ public static int parseAndWriteUnicode(StringWriter writer, String string, int i int codePoint = Integer.parseInt(hexStr, 16); if (isTrailingSurrogateValue(codePoint)) { - throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - trailing surrogate must be preceded with a leading surrogate -", null, string.substring(i - 1, continueIndex + 1), null); + throw new InvalidUnicodeSyntaxException(i18n, "InvalidUnicode.trailingLeadingSurrogate", sourceLocation, offendingToken(string, i, continueIndex)); } else if (isLeadingSurrogateValue(codePoint)) { if (!isEscapedUnicode(string, continueIndex + 1)) { - throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - leading surrogate must be followed by a trailing surrogate -", null, string.substring(i - 1, continueIndex + 1), null); + throw new InvalidUnicodeSyntaxException(i18n, "InvalidUnicode.leadingTrailingSurrogate", sourceLocation, offendingToken(string, i, continueIndex)); } // Shift parser ahead to 'u' in second escaped Unicode character i = continueIndex + 2; int trailingStartIndex = isBracedEscape(string, i) ? i + 2 : i + 1; - int trailingEndIndexExclusive = getEndIndexExclusive(string, i, sourceLocation); + int trailingEndIndexExclusive = getEndIndexExclusive(i18n, string, i, sourceLocation); String trailingHexStr = string.substring(trailingStartIndex, trailingEndIndexExclusive); int trailingCodePoint = Integer.parseInt(trailingHexStr, 16); continueIndex = isBracedEscape(string, i) ? trailingEndIndexExclusive : trailingEndIndexExclusive - 1; @@ -54,16 +56,20 @@ public static int parseAndWriteUnicode(StringWriter writer, String string, int i return continueIndex; } - throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - leading surrogate must be followed by a trailing surrogate -", null, string.substring(i - 1, continueIndex + 1), null); + throw new InvalidUnicodeSyntaxException(i18n, "InvalidUnicode.leadingTrailingSurrogate", sourceLocation, offendingToken(string, i, continueIndex)); } else if (isValidUnicodeCodePoint(codePoint)) { writeCodePoint(writer, codePoint); return continueIndex; } - throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - not a valid code point -", null, string.substring(i - 1, continueIndex + 1), null); + throw new InvalidUnicodeSyntaxException(i18n, "InvalidUnicode.invalidCodePoint", sourceLocation, offendingToken(string, i, continueIndex)); } - private static int getEndIndexExclusive(String string, int i, SourceLocation sourceLocation) { + private static String offendingToken(String string, int i, int continueIndex) { + return string.substring(i - 1, continueIndex + 1); + } + + private static int getEndIndexExclusive(I18n i18n, String string, int i, SourceLocation sourceLocation) { // Unbraced case, with exactly 4 hex digits if (string.length() > i + 5 && !isBracedEscape(string, i)) { return i + 5; @@ -73,7 +79,7 @@ private static int getEndIndexExclusive(String string, int i, SourceLocation sou int endIndexExclusive = i + 2; do { if (endIndexExclusive + 1 >= string.length()) { - throw new InvalidSyntaxException(sourceLocation, "Invalid unicode - incorrectly formatted escape -", null, string.substring(i - 1, endIndexExclusive), null); + throw new InvalidUnicodeSyntaxException(i18n, "InvalidUnicode.incorrectEscape", sourceLocation, string.substring(i - 1, endIndexExclusive)); } } while (string.charAt(++endIndexExclusive) != '}'); diff --git a/src/main/java/graphql/parser/exceptions/InvalidUnicodeSyntaxException.java b/src/main/java/graphql/parser/exceptions/InvalidUnicodeSyntaxException.java new file mode 100644 index 0000000000..be75127047 --- /dev/null +++ b/src/main/java/graphql/parser/exceptions/InvalidUnicodeSyntaxException.java @@ -0,0 +1,16 @@ +package graphql.parser.exceptions; + +import graphql.Internal; +import graphql.i18n.I18n; +import graphql.language.SourceLocation; +import graphql.parser.InvalidSyntaxException; +import org.jetbrains.annotations.NotNull; + +@Internal +public class InvalidUnicodeSyntaxException extends InvalidSyntaxException { + + public InvalidUnicodeSyntaxException(@NotNull I18n i18N, @NotNull String msgKey, @NotNull SourceLocation sourceLocation, @NotNull String offendingToken) { + super(i18N.msg(msgKey, offendingToken, sourceLocation.getLine(), sourceLocation.getColumn()), + sourceLocation, offendingToken, null, null); + } +} diff --git a/src/main/java/graphql/parser/exceptions/MoreTokensSyntaxException.java b/src/main/java/graphql/parser/exceptions/MoreTokensSyntaxException.java new file mode 100644 index 0000000000..6f73b38e4c --- /dev/null +++ b/src/main/java/graphql/parser/exceptions/MoreTokensSyntaxException.java @@ -0,0 +1,17 @@ +package graphql.parser.exceptions; + +import graphql.Internal; +import graphql.i18n.I18n; +import graphql.language.SourceLocation; +import graphql.parser.InvalidSyntaxException; +import org.jetbrains.annotations.NotNull; + +@Internal +public class MoreTokensSyntaxException extends InvalidSyntaxException { + + @Internal + public MoreTokensSyntaxException(@NotNull I18n i18N, @NotNull SourceLocation sourceLocation, @NotNull String offendingToken, @NotNull String sourcePreview) { + super(i18N.msg("InvalidSyntaxMoreTokens.full", offendingToken, sourceLocation.getLine(), sourceLocation.getColumn()), + sourceLocation, offendingToken, sourcePreview, null); + } +} diff --git a/src/main/java/graphql/parser/exceptions/ParseCancelledException.java b/src/main/java/graphql/parser/exceptions/ParseCancelledException.java new file mode 100644 index 0000000000..ab183367f3 --- /dev/null +++ b/src/main/java/graphql/parser/exceptions/ParseCancelledException.java @@ -0,0 +1,18 @@ +package graphql.parser.exceptions; + +import graphql.Internal; +import graphql.i18n.I18n; +import graphql.language.SourceLocation; +import graphql.parser.InvalidSyntaxException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Internal +public class ParseCancelledException extends InvalidSyntaxException { + + @Internal + public ParseCancelledException(@NotNull I18n i18N, @Nullable SourceLocation sourceLocation, @Nullable String offendingToken, int maxTokens, @NotNull String tokenType) { + super(i18N.msg("ParseCancelled.full", maxTokens, tokenType), + sourceLocation, offendingToken, null, null); + } +} diff --git a/src/main/java/graphql/schema/idl/SchemaParser.java b/src/main/java/graphql/schema/idl/SchemaParser.java index 097e5649f3..8fc0932367 100644 --- a/src/main/java/graphql/schema/idl/SchemaParser.java +++ b/src/main/java/graphql/schema/idl/SchemaParser.java @@ -8,6 +8,7 @@ import graphql.language.SDLDefinition; import graphql.parser.InvalidSyntaxException; import graphql.parser.Parser; +import graphql.parser.ParserEnvironment; import graphql.parser.ParserOptions; import graphql.schema.idl.errors.NonSDLDefinitionError; import graphql.schema.idl.errors.SchemaProblem; @@ -23,6 +24,7 @@ import java.util.Collections; import java.util.List; +import static graphql.parser.ParserEnvironment.newParserEnvironment; import static java.nio.charset.Charset.defaultCharset; /** @@ -116,8 +118,8 @@ private TypeDefinitionRegistry parseImpl(Reader schemaInput, ParserOptions parse if (parseOptions == null) { parseOptions = ParserOptions.getDefaultSdlParserOptions(); } - Parser parser = new Parser(); - Document document = parser.parseDocument(schemaInput, parseOptions); + ParserEnvironment parserEnvironment = newParserEnvironment().document(schemaInput).parserOptions(parseOptions).build(); + Document document = Parser.parse(parserEnvironment); return buildRegistry(document); } catch (InvalidSyntaxException e) { diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index a797535f0e..f2614ef02b 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -43,6 +43,7 @@ import graphql.language.VariableDefinition; import graphql.language.VariableReference; import graphql.parser.Parser; +import graphql.parser.ParserEnvironment; import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLArgument; @@ -92,6 +93,7 @@ import java.util.function.Consumer; import static graphql.Assert.assertNotNull; +import static graphql.parser.ParserEnvironment.newParserEnvironment; import static graphql.schema.GraphQLArgument.newArgument; import static graphql.schema.GraphQLTypeUtil.unwrapNonNull; import static graphql.schema.GraphQLTypeUtil.unwrapNonNullAs; @@ -715,7 +717,8 @@ private static String rewriteQuery(String query, GraphQLSchema schema, Map astNodeToNewName = new LinkedHashMap<>(); Map variableNames = new LinkedHashMap<>(); Map fieldToFieldDefinition = new LinkedHashMap<>(); - Document document = new Parser().parseDocument(query); + + Document document = Parser.parse(newParserEnvironment().document(query).build()); assertUniqueOperation(document); QueryTraverser queryTraverser = QueryTraverser.newQueryTraverser().document(document).schema(schema).variables(variables).build(); queryTraverser.visitDepthFirst(new QueryVisitor() { diff --git a/src/main/resources/i18n/Parsing.properties b/src/main/resources/i18n/Parsing.properties new file mode 100644 index 0000000000..1152e601e5 --- /dev/null +++ b/src/main/resources/i18n/Parsing.properties @@ -0,0 +1,26 @@ +# +# This resource bundle is used for the query parsing code to produce i18n messages +# +# The keys have the format of rule class name and then message type within that. Most rules +# will only have 1 or 2 message keys +# +# Please try and keep this sorted within rule class and use # between sections so the IDEA Ctrl-Alt-L reformat does not bunch +# them too tightly. +# +# REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them +# so use 2 '' characters to make it one ' on output. This will take for the form ''{0}'' +# +InvalidSyntax.noMessage=Invalid syntax at line {0} column {1} +InvalidSyntax.full=Invalid syntax with ANTLR error ''{0}'' at line {1} column {2} + +InvalidSyntaxBail.noToken=Invalid syntax at line {0} column {1} +InvalidSyntaxBail.full=Invalid syntax with offending token ''{0}'' at line {1} column {2} +# +InvalidSyntaxMoreTokens.full=Invalid syntax encountered. There are extra tokens in the text that have not been consumed. Offending token ''{0}'' at line {1} column {2} +# +ParseCancelled.full=More than {0} ''{1}'' tokens have been presented. To prevent Denial Of Service attacks, parsing has been cancelled. +# +InvalidUnicode.trailingLeadingSurrogate=Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token ''{0}'' at line {1} column {2} +InvalidUnicode.leadingTrailingSurrogate=Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token ''{0}'' at line {1} column {2} +InvalidUnicode.invalidCodePoint=Invalid unicode encountered. Not a valid code point. Offending token ''{0}'' at line {1} column {2} +InvalidUnicode.incorrectEscape=Invalid unicode encountered. Incorrectly formatted escape sequence. Offending token ''{0}'' at line {1} column {2} diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index d816869e87..505350e74b 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -223,7 +223,7 @@ class GraphQLTest extends Specification { then: errors.size() == 1 errors[0].errorType == ErrorType.InvalidSyntax - errors[0].message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\ud83c' at line 1 column 13" + errors[0].message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\ud83c' at line 1 column 13" errors[0].locations == [new SourceLocation(1, 13)] } diff --git a/src/test/groovy/graphql/ParseAndValidateTest.groovy b/src/test/groovy/graphql/ParseAndValidateTest.groovy index 3b676bd8d4..949b4aeb5e 100644 --- a/src/test/groovy/graphql/ParseAndValidateTest.groovy +++ b/src/test/groovy/graphql/ParseAndValidateTest.groovy @@ -110,7 +110,7 @@ class ParseAndValidateTest extends Specification { result.variables == [var1: 1] result.syntaxException != null - (result.errors[0] as InvalidSyntaxError).message.contains("Invalid Syntax") + (result.errors[0] as InvalidSyntaxError).message.contains("Invalid syntax") } def "can use the graphql context to stop certain validation rules"() { diff --git a/src/test/groovy/graphql/parser/ParserTest.groovy b/src/test/groovy/graphql/parser/ParserTest.groovy index 8f461ec182..6dcf336742 100644 --- a/src/test/groovy/graphql/parser/ParserTest.groovy +++ b/src/test/groovy/graphql/parser/ParserTest.groovy @@ -40,6 +40,7 @@ import graphql.language.TypeName import graphql.language.UnionTypeDefinition import graphql.language.VariableDefinition import graphql.language.VariableReference +import graphql.parser.exceptions.ParseCancelledException import org.antlr.v4.runtime.CommonTokenStream import org.antlr.v4.runtime.ParserRuleContext import spock.lang.Issue @@ -605,7 +606,7 @@ class ParserTest extends Specification { then: def e = thrown(InvalidSyntaxException) - e.message.contains("Invalid Syntax") + e.message.contains("Invalid syntax") } def "three quotation marks is an illegal string"() { @@ -617,7 +618,7 @@ class ParserTest extends Specification { then: def e = thrown(InvalidSyntaxException) - e.message.contains("Invalid Syntax") + e.message.contains("Invalid syntax") } def "escaped triple quote inside block string"() { @@ -836,7 +837,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" println document then: def e = thrown(InvalidSyntaxException) - e.message.contains("Invalid Syntax") + e.message.contains("Invalid syntax") e.sourcePreview == input + "\n" e.location.line == 3 e.location.column == 20 @@ -886,9 +887,9 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" when: Parser parser = new Parser() { @Override - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserEnvironment environment) { // this pattern is used in Nadel - its backdoor but needed - return new GraphqlAntlrToLanguage(tokens, multiSourceReader) { + return new GraphqlAntlrToLanguage(tokens, multiSourceReader, environment.parserOptions, environment.i18N, null) { @Override protected void addCommonData(NodeBuilder nodeBuilder, ParserRuleContext parserRuleContext) { super.addCommonData(nodeBuilder, parserRuleContext) @@ -908,8 +909,8 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" parser = new Parser() { @Override - protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) { - return new GraphqlAntlrToLanguage(tokens, multiSourceReader, parserOptions) { + protected GraphqlAntlrToLanguage getAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserEnvironment environment) { + return new GraphqlAntlrToLanguage(tokens, multiSourceReader, environment.parserOptions, environment.i18N, null) { @Override protected void addCommonData(NodeBuilder nodeBuilder, ParserRuleContext parserRuleContext) { super.addCommonData(nodeBuilder, parserRuleContext) @@ -951,7 +952,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: def e = thrown(InvalidSyntaxException) - e.message.contains("Invalid Syntax") + e.message.contains("Invalid syntax") where: value | _ '00' | _ @@ -971,7 +972,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: def e = thrown(InvalidSyntaxException) - e.message.contains("Invalid Syntax") + e.message.contains("Invalid syntax") where: value | _ '01.23' | _ @@ -1126,7 +1127,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\ud83c' at line 3 column 24" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\ud83c' at line 3 column 24" } def "invalid surrogate pair - no leading value"() { @@ -1142,7 +1143,7 @@ triple3 : """edge cases \\""" "" " \\"" \\" edge cases""" then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - trailing surrogate must be preceded with a leading surrogate - offending token '\\uDC00' at line 3 column 24" + e.message == "Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token '\\uDC00' at line 3 column 24" } def "source locations are on by default but can be turned off"() { diff --git a/src/test/groovy/graphql/parser/StringValueParsingTest.groovy b/src/test/groovy/graphql/parser/StringValueParsingTest.groovy index 5543dbc339..2142c84175 100644 --- a/src/test/groovy/graphql/parser/StringValueParsingTest.groovy +++ b/src/test/groovy/graphql/parser/StringValueParsingTest.groovy @@ -1,5 +1,7 @@ package graphql.parser +import graphql.i18n.I18n +import graphql.language.SourceLocation import spock.lang.Specification import static java.util.Arrays.asList @@ -7,12 +9,15 @@ import static java.util.stream.Collectors.joining class StringValueParsingTest extends Specification { + def i18n = I18n.i18n(I18n.BundleType.Parsing, Locale.ENGLISH) + def sourceLocation = SourceLocation.EMPTY + def "parsing quoted string should work"() { given: def input = '''"simple quoted"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == "simple quoted" @@ -23,7 +28,7 @@ class StringValueParsingTest extends Specification { def input = '''"{\"name\": \"graphql\", \"year\": 2015}"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''{\"name\": \"graphql\", \"year\": 2015}''' @@ -34,7 +39,7 @@ class StringValueParsingTest extends Specification { def input = '''"""''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''"''' @@ -45,7 +50,7 @@ class StringValueParsingTest extends Specification { def input = '''"\\ud83c\\udf7a"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''🍺''' // contains the beer icon U+1F37A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -56,7 +61,7 @@ class StringValueParsingTest extends Specification { def input = '''"\\u5564\\u9152"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''啤酒''' diff --git a/src/test/groovy/graphql/parser/StringValueParsingUnicodeTest.groovy b/src/test/groovy/graphql/parser/StringValueParsingUnicodeTest.groovy index 63c59d8011..192f3b0097 100644 --- a/src/test/groovy/graphql/parser/StringValueParsingUnicodeTest.groovy +++ b/src/test/groovy/graphql/parser/StringValueParsingUnicodeTest.groovy @@ -1,12 +1,14 @@ package graphql.parser -import graphql.language.Document -import graphql.language.Field -import graphql.language.OperationDefinition -import graphql.language.StringValue +import graphql.i18n.I18n +import graphql.language.SourceLocation import spock.lang.Specification class StringValueParsingUnicodeTest extends Specification { + + def i18n = I18n.i18n(I18n.BundleType.Parsing, Locale.ENGLISH) + def sourceLocation = SourceLocation.EMPTY + /** * Implements RFC to support full Unicode https://github.com/graphql/graphql-spec/pull/849 * @@ -25,7 +27,7 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\u{1F37A} hello"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''🍺 hello''' // contains the beer icon U+1F37A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -36,7 +38,7 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"🍺 hello"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''🍺 hello''' // contains the beer icon U+1F37A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -60,11 +62,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uD83D hello"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\uD83D'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\uD83D' at line -1 column -1" } def "invalid surrogate pair - end of string"() { @@ -72,11 +74,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uD83D"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\uD83D'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\uD83D' at line -1 column -1" } def "invalid surrogate pair - invalid trailing value"() { @@ -84,11 +86,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uD83D\\uDBFF"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\uDBFF'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\uDBFF' at line -1 column -1" } def "invalid surrogate pair - no leading value"() { @@ -96,11 +98,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uDC00"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - trailing surrogate must be preceded with a leading surrogate - offending token '\\uDC00'" + e.message == "Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token '\\uDC00' at line -1 column -1" } def "invalid surrogate pair - invalid leading value"() { @@ -108,11 +110,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uD700\\uDC00"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - trailing surrogate must be preceded with a leading surrogate - offending token '\\uDC00'" + e.message == "Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token '\\uDC00' at line -1 column -1" } def "valid surrogate pair - leading code with braces"() { @@ -120,7 +122,7 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}\\udf7a"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''hello 🍺''' // contains the beer icon U+1F37 A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -131,7 +133,7 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\ud83c\\u{df7a}"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''hello 🍺''' // contains the beer icon U+1F37A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -142,7 +144,7 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}\\u{df7a}"''' when: - String parsed = StringValueParsing.parseSingleQuotedString(input) + String parsed = StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: parsed == '''hello 🍺''' // contains the beer icon U+1F37A : http://www.charbase.com/1f37a-unicode-beer-mug @@ -153,11 +155,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}\\"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\u{d83c}'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\u{d83c}' at line -1 column -1" } def "invalid surrogate pair - leading code with only \\u at end of string"() { @@ -165,11 +167,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}\\u"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - incorrectly formatted escape - offending token '\\u\"'" + e.message == "Invalid unicode encountered. Incorrectly formatted escape sequence. Offending token '\\u\"' at line -1 column -1" } def "invalid surrogate pair - trailing code without closing brace"() { @@ -177,11 +179,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}\\u{df7a"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - incorrectly formatted escape - offending token '\\u{df7a'" + e.message == "Invalid unicode encountered. Incorrectly formatted escape sequence. Offending token '\\u{df7a' at line -1 column -1" } def "invalid surrogate pair - invalid trailing code without unicode escape 1"() { @@ -189,11 +191,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}{df7a}"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\u{d83c}'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\u{d83c}' at line -1 column -1" } def "invalid surrogate pair - invalid trailing code without unicode escape 2"() { @@ -201,11 +203,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello \\u{d83c}df7a"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\u{d83c}'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\u{d83c}' at line -1 column -1" } def "invalid surrogate pair - invalid leading code"() { @@ -213,11 +215,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"hello d83c\\u{df7a}"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - trailing surrogate must be preceded with a leading surrogate - offending token '\\u{df7a}'" + e.message == "Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token '\\u{df7a}' at line -1 column -1" } def "invalid surrogate pair - invalid leading value with braces"() { @@ -225,11 +227,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\u{5B57}\\uDC00"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - trailing surrogate must be preceded with a leading surrogate - offending token '\\uDC00'" + e.message == "Invalid unicode encountered. Trailing surrogate must be preceded with a leading surrogate. Offending token '\\uDC00' at line -1 column -1" } def "invalid surrogate pair - invalid trailing value with braces"() { @@ -237,11 +239,11 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\uD83D\\u{DBFF}"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - leading surrogate must be followed by a trailing surrogate - offending token '\\u{DBFF}'" + e.message == "Invalid unicode encountered. Leading surrogate must be followed by a trailing surrogate. Offending token '\\u{DBFF}' at line -1 column -1" } def "invalid unicode code point - value is too high"() { @@ -249,10 +251,10 @@ class StringValueParsingUnicodeTest extends Specification { def input = '''"\\u{fffffff}"''' when: - StringValueParsing.parseSingleQuotedString(input) + StringValueParsing.parseSingleQuotedString(i18n, input,sourceLocation) then: InvalidSyntaxException e = thrown(InvalidSyntaxException) - e.message == "Invalid Syntax : Invalid unicode - not a valid code point - offending token '\\u{fffffff}'" + e.message == "Invalid unicode encountered. Not a valid code point. Offending token '\\u{fffffff}' at line -1 column -1" } } From 448c9657c88aef0f8b75255d0139aab8e7e6a4c9 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 16 Oct 2022 12:16:23 +1100 Subject: [PATCH 182/294] Uncomment Parsing translations --- src/main/resources/i18n/Parsing_de.properties | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/resources/i18n/Parsing_de.properties b/src/main/resources/i18n/Parsing_de.properties index c8569e1b73..53fa002a5b 100644 --- a/src/main/resources/i18n/Parsing_de.properties +++ b/src/main/resources/i18n/Parsing_de.properties @@ -13,18 +13,17 @@ # Prior to Java 9, properties files are encoded in ISO-8859-1. # We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss # -# TODO: uncomment these messages after the default (English) Parsing bundle is merged in -#InvalidSyntax.noMessage=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} -#InvalidSyntax.full=Ung\u00fcltige Syntax mit ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} -# -#InvalidSyntaxBail.noToken=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} -#InvalidSyntaxBail.full=Ung\u00fcltige Syntax mit ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} -## -#InvalidSyntaxMoreTokens.full=Es wurde eine ung\u00fcltige Syntax festgestellt. Es gibt zus\u00e4tzliche Token im Text, die nicht konsumiert wurden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -## -#ParseCancelled.full=Es wurden mehr als {0} ''{1}'' Token pr\u00e4sentiert. Um Denial-of-Service-Angriffe zu verhindern, wurde das Parsing abgebrochen -## -#InvalidUnicode.trailingLeadingSurrogate=Ung\u00fcltiger Unicode gefunden. Nachfolgendes Surrogat muss ein f\u00fchrendes Surrogat vorangestellt werden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -#InvalidUnicode.leadingTrailingSurrogate=Ung\u00fcltiger Unicode gefunden. Auf ein f\u00fchrendes Surrogat muss ein abschlie\u00dfendes Surrogat folgen. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -#InvalidUnicode.invalidCodePoint=Ung\u00fcltiger Unicode gefunden. Kein g\u00fcltiger Codepunkt. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -#InvalidUnicode.incorrectEscape=Ung\u00fcltiger Unicode gefunden. Falsch formatierte Escape-Sequenz. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} \ No newline at end of file +InvalidSyntax.noMessage=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} +InvalidSyntax.full=Ung\u00fcltige Syntax mit ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} + +InvalidSyntaxBail.noToken=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} +InvalidSyntaxBail.full=Ung\u00fcltige Syntax mit ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} +# +InvalidSyntaxMoreTokens.full=Es wurde eine ung\u00fcltige Syntax festgestellt. Es gibt zus\u00e4tzliche Token im Text, die nicht konsumiert wurden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +# +ParseCancelled.full=Es wurden mehr als {0} ''{1}'' Token pr\u00e4sentiert. Um Denial-of-Service-Angriffe zu verhindern, wurde das Parsing abgebrochen +# +InvalidUnicode.trailingLeadingSurrogate=Ung\u00fcltiger Unicode gefunden. Nachfolgendes Surrogat muss ein f\u00fchrendes Surrogat vorangestellt werden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.leadingTrailingSurrogate=Ung\u00fcltiger Unicode gefunden. Auf ein f\u00fchrendes Surrogat muss ein abschlie\u00dfendes Surrogat folgen. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.invalidCodePoint=Ung\u00fcltiger Unicode gefunden. Kein g\u00fcltiger Codepunkt. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.incorrectEscape=Ung\u00fcltiger Unicode gefunden. Falsch formatierte Escape-Sequenz. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} \ No newline at end of file From 4024420ace5d4cd44af3b9a035a8b35dee45be1b Mon Sep 17 00:00:00 2001 From: Paul Gross Date: Mon, 17 Oct 2022 22:26:20 -0700 Subject: [PATCH 183/294] Add missing equals/hashcode methods to relay classes (#2988) Add equals/hashcode methods to DefaultConnection, DefaultEdge, DefaultPageInfo. https://github.com/graphql-java/graphql-java/discussions/2986 --- .../java/graphql/relay/DefaultConnection.java | 18 ++++++++++++ src/main/java/graphql/relay/DefaultEdge.java | 19 ++++++++++++ .../java/graphql/relay/DefaultPageInfo.java | 22 ++++++++++++++ .../relay/DefaultConnectionTest.groovy | 27 +++++++++++++++++ .../graphql/relay/DefaultEdgeTest.groovy | 25 ++++++++++++++++ .../graphql/relay/DefaultPageInfoTest.groovy | 29 +++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 src/test/groovy/graphql/relay/DefaultConnectionTest.groovy create mode 100644 src/test/groovy/graphql/relay/DefaultEdgeTest.groovy create mode 100644 src/test/groovy/graphql/relay/DefaultPageInfoTest.groovy diff --git a/src/main/java/graphql/relay/DefaultConnection.java b/src/main/java/graphql/relay/DefaultConnection.java index 42729d898e..e6db4dc4ea 100644 --- a/src/main/java/graphql/relay/DefaultConnection.java +++ b/src/main/java/graphql/relay/DefaultConnection.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; +import java.util.Objects; import static graphql.Assert.assertNotNull; @@ -40,6 +41,23 @@ public PageInfo getPageInfo() { return pageInfo; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultConnection that = (DefaultConnection) o; + return Objects.equals(edges, that.edges) && Objects.equals(pageInfo, that.pageInfo); + } + + @Override + public int hashCode() { + return Objects.hash(edges, pageInfo); + } + @Override public String toString() { return "DefaultConnection{" + diff --git a/src/main/java/graphql/relay/DefaultEdge.java b/src/main/java/graphql/relay/DefaultEdge.java index eb29b66e19..e43f4c54ce 100644 --- a/src/main/java/graphql/relay/DefaultEdge.java +++ b/src/main/java/graphql/relay/DefaultEdge.java @@ -2,6 +2,8 @@ import graphql.PublicApi; +import java.util.Objects; + import static graphql.Assert.assertNotNull; @PublicApi @@ -26,6 +28,23 @@ public ConnectionCursor getCursor() { return cursor; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultEdge that = (DefaultEdge) o; + return Objects.equals(node, that.node) && Objects.equals(cursor, that.cursor); + } + + @Override + public int hashCode() { + return Objects.hash(node, cursor); + } + @Override public String toString() { return "DefaultEdge{" + diff --git a/src/main/java/graphql/relay/DefaultPageInfo.java b/src/main/java/graphql/relay/DefaultPageInfo.java index 6e20d073c2..d76ef04fb1 100644 --- a/src/main/java/graphql/relay/DefaultPageInfo.java +++ b/src/main/java/graphql/relay/DefaultPageInfo.java @@ -3,6 +3,8 @@ import graphql.PublicApi; +import java.util.Objects; + @PublicApi public class DefaultPageInfo implements PageInfo { @@ -39,6 +41,26 @@ public boolean isHasNextPage() { return hasNextPage; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultPageInfo that = (DefaultPageInfo) o; + return Objects.equals(startCursor, that.startCursor) && + Objects.equals(endCursor, that.endCursor) && + Objects.equals(hasPreviousPage, that.hasPreviousPage) && + Objects.equals(hasNextPage, that.hasNextPage); + } + + @Override + public int hashCode() { + return Objects.hash(startCursor, endCursor, hasPreviousPage, hasNextPage); + } + @Override public String toString() { return "DefaultPageInfo{" + diff --git a/src/test/groovy/graphql/relay/DefaultConnectionTest.groovy b/src/test/groovy/graphql/relay/DefaultConnectionTest.groovy new file mode 100644 index 0000000000..592b18c968 --- /dev/null +++ b/src/test/groovy/graphql/relay/DefaultConnectionTest.groovy @@ -0,0 +1,27 @@ +package graphql.relay + +import com.google.common.collect.ImmutableList +import spock.lang.Specification + +class DefaultConnectionTest extends Specification { + + def "test equality and hashcode"() { + def edges1 = ImmutableList.of(new DefaultEdge("a", new DefaultConnectionCursor("a"))) + def edges2 = ImmutableList.of(new DefaultEdge("b", new DefaultConnectionCursor("b"))) + + def pageInfo1 = new DefaultPageInfo(new DefaultConnectionCursor("c"), new DefaultConnectionCursor("c"), true, false) + def pageInfo2 = new DefaultPageInfo(new DefaultConnectionCursor("d"), new DefaultConnectionCursor("d"), false, true) + + expect: + + assert new DefaultConnection(edges1, pageInfo1).equals(new DefaultConnection(edges1, pageInfo1)) + assert !new DefaultConnection(edges1, pageInfo2).equals(new DefaultConnection(edges1, pageInfo1)) + assert !new DefaultConnection(edges1, pageInfo1).equals(new DefaultConnection(edges2, pageInfo1)) + assert !new DefaultConnection(edges1, pageInfo1).equals(new DefaultConnection(edges1, pageInfo2)) + + assert new DefaultConnection(edges1, pageInfo1).hashCode() == new DefaultConnection(edges1, pageInfo1).hashCode() + assert new DefaultConnection(edges1, pageInfo2).hashCode() != new DefaultConnection(edges1, pageInfo1).hashCode() + assert new DefaultConnection(edges1, pageInfo1).hashCode() != new DefaultConnection(edges2, pageInfo1).hashCode() + assert new DefaultConnection(edges1, pageInfo1).hashCode() != new DefaultConnection(edges1, pageInfo2).hashCode() + } +} diff --git a/src/test/groovy/graphql/relay/DefaultEdgeTest.groovy b/src/test/groovy/graphql/relay/DefaultEdgeTest.groovy new file mode 100644 index 0000000000..3763a0c7ab --- /dev/null +++ b/src/test/groovy/graphql/relay/DefaultEdgeTest.groovy @@ -0,0 +1,25 @@ +package graphql.relay + +import spock.lang.Specification + +class DefaultEdgeTest extends Specification { + + def "test equality and hashcode"() { + expect: + + assert new DefaultEdge(leftNode, new DefaultConnectionCursor(leftConnectionCursor)).equals( + new DefaultEdge(rightNode, new DefaultConnectionCursor(rightConnectionCursor))) == isEqual + + assert (new DefaultEdge(leftNode, new DefaultConnectionCursor(leftConnectionCursor)).hashCode() == + new DefaultEdge(rightNode, new DefaultConnectionCursor(rightConnectionCursor)).hashCode()) == isEqual + + where: + + leftNode | leftConnectionCursor | rightNode | rightConnectionCursor || isEqual + "a" | "b" | "a" | "b" || true + "x" | "b" | "a" | "b" || false + "a" | "x" | "a" | "b" || false + "a" | "b" | "x" | "b" || false + "a" | "b" | "a" | "x" || false + } +} diff --git a/src/test/groovy/graphql/relay/DefaultPageInfoTest.groovy b/src/test/groovy/graphql/relay/DefaultPageInfoTest.groovy new file mode 100644 index 0000000000..2e370467cb --- /dev/null +++ b/src/test/groovy/graphql/relay/DefaultPageInfoTest.groovy @@ -0,0 +1,29 @@ +package graphql.relay + +import spock.lang.Specification + +class DefaultPageInfoTest extends Specification { + + def "test equality and hashcode"() { + expect: + + assert new DefaultPageInfo(new DefaultConnectionCursor(ls), new DefaultConnectionCursor(le), lp, ln).equals( + new DefaultPageInfo(new DefaultConnectionCursor(rs), new DefaultConnectionCursor(re), rp, rn)) == isEqual + + assert (new DefaultPageInfo(new DefaultConnectionCursor(ls), new DefaultConnectionCursor(le), lp, ln).hashCode() == + new DefaultPageInfo(new DefaultConnectionCursor(rs), new DefaultConnectionCursor(re), rp, rn).hashCode()) == isEqual + + where: + + ls | le | lp | ln | rs | re | rp | rn || isEqual + "a" | "b" | true | true | "a" | "b" | true | true || true + "x" | "b" | true | true | "a" | "b" | true | true || false + "a" | "x" | true | true | "a" | "b" | true | true || false + "a" | "b" | false | true | "a" | "b" | true | true || false + "a" | "b" | true | false | "a" | "b" | true | true || false + "a" | "b" | true | true | "x" | "b" | true | true || false + "a" | "b" | true | true | "a" | "x" | true | true || false + "a" | "b" | true | true | "a" | "b" | false | true || false + "a" | "b" | true | true | "a" | "b" | true | false || false + } +} From 49482b116cefe334343acf4b6eb6d89470547c57 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 21 Oct 2022 08:39:44 +1000 Subject: [PATCH 184/294] object field type changes --- .../schema/diffing/SchemaGraphFactory.java | 2 +- .../diffing/ana/EditOperationAnalyzer.java | 48 +++++++++++++++---- .../schema/diffing/ana/ObjectModified.java | 32 +++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++-- 4 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index caead1493a..74b70ab064 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -217,7 +217,7 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit // schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(fieldVertex, typeVertex); - typeEdge.setLabel(GraphQLTypeUtil.simplePrint(type)); + typeEdge.setLabel("type=" + GraphQLTypeUtil.simplePrint(type) + ";"); schemaGraph.addEdge(typeEdge); for (GraphQLArgument graphQLArgument : fieldDefinition.getArguments()) { diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b52193c523..d6614a8dc7 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -64,7 +64,7 @@ private void handleOtherChanges(List editOperations) { } break; case INSERT_VERTEX: - if(editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { + if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldAdded(editOperation); } } @@ -87,7 +87,7 @@ private void fieldAdded(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { - if(isNewObject(fieldsContainerForField.getName())) { + if (isNewObject(fieldsContainerForField.getName())) { return; } ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); @@ -193,9 +193,9 @@ private void handleEdgeChanges(List editOperations) { // case DELETE_EDGE: // deletedEdge(editOperation); // break; -// case CHANGE_EDGE: -// changedEdge(editOperation); -// break; + case CHANGE_EDGE: + changedEdge(editOperation); + break; } } } @@ -205,9 +205,43 @@ private void insertedEdge(EditOperation editOperation) { if (newEdge.getLabel().startsWith("implements ")) { newInterfaceAddedToInterfaceOrObject(newEdge); } -// else if(newEdge) } + private void changedEdge(EditOperation editOperation) { + Edge newEdge = editOperation.getTargetEdge(); + if (newEdge.getLabel().startsWith("type=")) { + typeEdgeChanged(editOperation); + } + } + + private void typeEdgeChanged(EditOperation editOperation) { + Edge targetEdge = editOperation.getTargetEdge(); + Vertex from = targetEdge.getFrom(); + Vertex to = targetEdge.getTo(); + // edge goes from FIELD to the TYPE + if (from.isOfType(SchemaGraph.FIELD)) { + Vertex field = from; + Vertex container = newSchemaGraph.getFieldsContainerForField(field); + if (container.isOfType(SchemaGraph.OBJECT)) { + Vertex object = container; + ObjectModified objectModified = getObjectModified(object.getName()); + String fieldName = field.getName(); + String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldTypeModified(fieldName, oldType, newType)); + } + } + } + + // TODO: this is not great, we should avoid parsing string like that + private String getTypeFromEdgeLabel(Edge edge) { + String label = edge.getLabel(); + assertTrue(label.startsWith("type=")); + String type = label.substring("type=".length(), label.indexOf(";")); + return type; + } + + private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { Vertex from = newEdge.getFrom(); if (from.isOfType(SchemaGraph.OBJECT)) { @@ -258,7 +292,6 @@ private InterfaceModified getInterfaceModified(String newName) { } - private void addedObject(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); ObjectAdded objectAdded = new ObjectAdded(objectName); @@ -306,7 +339,6 @@ private void addedScalar(EditOperation editOperation) { } - private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java index 8bf9c9ddfd..98a65fb4e3 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -1,6 +1,7 @@ package graphql.schema.diffing.ana; import graphql.ExperimentalApi; +import graphql.util.FpKit; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,7 @@ public String getName() { return name; } } + public static class RemovedInterfaceToObjectDetail implements ObjectModifiedDetails { private final String name; @@ -37,6 +39,7 @@ public String getName() { return name; } } + public static class FieldRenamed implements ObjectModifiedDetails { private final String oldName; private final String newName; @@ -54,6 +57,7 @@ public String getOldName() { return oldName; } } + public static class FieldAdded implements ObjectModifiedDetails { private final String name; @@ -66,6 +70,30 @@ public String getName() { } } + public static class FieldTypeModified implements ObjectModifiedDetails { + private final String name; + private final String oldType; + private final String newType; + + public FieldTypeModified(String name, String oldType, String newType) { + this.name = name; + this.oldType = oldType; + this.newType = newType; + } + + public String getName() { + return name; + } + + public String getOldType() { + return oldType; + } + + public String getNewType() { + return newType; + } + } + public ObjectModified(String name) { this.name = name; @@ -80,4 +108,8 @@ public String getName() { public List getObjectModifiedDetails() { return objectModifiedDetails; } + + public List getObjectModifiedDetails(Class clazz) { + return (List) FpKit.filterList(objectModifiedDetails, clazz::isInstance); + } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 1c086a27fe..326492fe53 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -8,7 +8,7 @@ import static graphql.schema.diffing.ana.SchemaChanges.* class EditOperationAnalyzerTest extends Specification { - def "field name changed"() { + def "field renamed"() { given: def oldSdl = ''' type Query { @@ -30,7 +30,29 @@ class EditOperationAnalyzerTest extends Specification { fieldRenames[0].newName == "hello2" } - def "test field added"() { + def "field type modified"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello: String! + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModified + def objectModified = changes.objectChanges["Query"] as ObjectModified + def fieldRenames = objectModified.getObjectModifiedDetails(ObjectModified.FieldTypeModified.class); + fieldRenames[0].oldType == "String" + fieldRenames[0].newType == "String!" + } + + def "field added"() { given: def oldSdl = ''' type Query { @@ -53,7 +75,7 @@ class EditOperationAnalyzerTest extends Specification { fieldAdded[0].name == "newOne" } - def "test Object added"() { + def "Object added"() { given: def oldSdl = ''' type Query { From 073b81b245fe2d5db56f3d74ffcae63c97a48eda Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 21 Oct 2022 09:20:02 +1000 Subject: [PATCH 185/294] object field type changes --- .../diffing/ana/EditOperationAnalyzer.java | 25 ++++++++++++++++- .../schema/diffing/ana/ObjectModified.java | 19 +++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index d6614a8dc7..67dc7bec66 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -67,6 +67,11 @@ private void handleOtherChanges(List editOperations) { if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldAdded(editOperation); } + break; + case DELETE_VERTEX: + if (editOperation.getSourceVertex().isOfType(SchemaGraph.ARGUMENT)) { + removedArgument(editOperation); + } } } @@ -233,7 +238,7 @@ private void typeEdgeChanged(EditOperation editOperation) { } } - // TODO: this is not great, we should avoid parsing string like that + // TODO: this is not great, we should avoid parsing the label like that private String getTypeFromEdgeLabel(Edge edge) { String label = edge.getLabel(); assertTrue(label.startsWith("type=")); @@ -271,6 +276,10 @@ private boolean isNewObject(String name) { return objectChanges.containsKey(name) && objectChanges.get(name) instanceof ObjectAdded; } + private boolean isRemovedObject(String name) { + return objectChanges.containsKey(name) && objectChanges.get(name) instanceof ObjectRemoved; + } + private boolean isNewInterface(String name) { return interfaceChanges.containsKey(name) && interfaceChanges.get(name) instanceof InterfaceAdded; } @@ -381,6 +390,20 @@ private void removedScalar(EditOperation editOperation) { scalarChanges.put(scalarName, change); } + private void removedArgument(EditOperation editOperation) { + Vertex removedArgument = editOperation.getSourceVertex(); + Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(removedArgument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + Vertex field = fieldOrDirective; + Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(field); + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + Vertex object = fieldsContainerForField; + getObjectModified(object.getName()).getObjectModifiedDetails().add(new ObjectModified.ArgumentRemoved(field.getName(), removedArgument.getName())); + } + } + + } + private void modifiedObject(EditOperation editOperation) { // // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java index 98a65fb4e3..e1749c0362 100644 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java @@ -94,6 +94,25 @@ public String getNewType() { } } + public static class ArgumentRemoved implements ObjectModifiedDetails { + + private final String fieldName; + private final String argumentName; + + public ArgumentRemoved(String fieldName, String argumentName) { + this.fieldName = fieldName; + this.argumentName = argumentName; + } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } + } + public ObjectModified(String name) { this.name = name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 326492fe53..f47b469472 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -47,9 +47,31 @@ class EditOperationAnalyzerTest extends Specification { then: changes.objectChanges["Query"] instanceof ObjectModified def objectModified = changes.objectChanges["Query"] as ObjectModified - def fieldRenames = objectModified.getObjectModifiedDetails(ObjectModified.FieldTypeModified.class); - fieldRenames[0].oldType == "String" - fieldRenames[0].newType == "String!" + def typeModified = objectModified.getObjectModifiedDetails(ObjectModified.FieldTypeModified.class); + typeModified[0].oldType == "String" + typeModified[0].newType == "String!" + } + + def "argument removed"() { + given: + def oldSdl = ''' + type Query { + hello(arg: String): String + } + ''' + def newSdl = ''' + type Query { + hello: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModified + def objectModified = changes.objectChanges["Query"] as ObjectModified + def argumentRemoved = objectModified.getObjectModifiedDetails(ObjectModified.ArgumentRemoved.class); + argumentRemoved[0].fieldName == "hello" + argumentRemoved[0].argumentName == "arg" } def "field added"() { From 744ebdd0f33b9256eba9c8639368d0d25cc791b3 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 21 Oct 2022 16:10:59 +1100 Subject: [PATCH 186/294] Update after comments --- src/main/resources/i18n/Parsing_de.properties | 10 ++-- src/main/resources/i18n/Scalars_de.properties | 10 ++-- .../resources/i18n/Validation_de.properties | 46 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/resources/i18n/Parsing_de.properties b/src/main/resources/i18n/Parsing_de.properties index 53fa002a5b..8b2d73b430 100644 --- a/src/main/resources/i18n/Parsing_de.properties +++ b/src/main/resources/i18n/Parsing_de.properties @@ -14,16 +14,16 @@ # We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss # InvalidSyntax.noMessage=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} -InvalidSyntax.full=Ung\u00fcltige Syntax mit ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} +InvalidSyntax.full=Ung\u00fcltige Syntax, ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} InvalidSyntaxBail.noToken=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} -InvalidSyntaxBail.full=Ung\u00fcltige Syntax mit ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} +InvalidSyntaxBail.full=Ung\u00fcltige Syntax wegen ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} # InvalidSyntaxMoreTokens.full=Es wurde eine ung\u00fcltige Syntax festgestellt. Es gibt zus\u00e4tzliche Token im Text, die nicht konsumiert wurden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} # ParseCancelled.full=Es wurden mehr als {0} ''{1}'' Token pr\u00e4sentiert. Um Denial-of-Service-Angriffe zu verhindern, wurde das Parsing abgebrochen # -InvalidUnicode.trailingLeadingSurrogate=Ung\u00fcltiger Unicode gefunden. Nachfolgendes Surrogat muss ein f\u00fchrendes Surrogat vorangestellt werden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -InvalidUnicode.leadingTrailingSurrogate=Ung\u00fcltiger Unicode gefunden. Auf ein f\u00fchrendes Surrogat muss ein abschlie\u00dfendes Surrogat folgen. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} -InvalidUnicode.invalidCodePoint=Ung\u00fcltiger Unicode gefunden. Kein g\u00fcltiger Codepunkt. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.trailingLeadingSurrogate=Ung\u00fcltiger Unicode gefunden. Trailing surrogate muss ein leading surrogate vorangestellt werden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.leadingTrailingSurrogate=Ung\u00fcltiger Unicode gefunden. Auf ein leading surrogate muss ein trailing surrogate folgen. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} +InvalidUnicode.invalidCodePoint=Ung\u00fcltiger Unicode gefunden. Kein g\u00fcltiger code point. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} InvalidUnicode.incorrectEscape=Ung\u00fcltiger Unicode gefunden. Falsch formatierte Escape-Sequenz. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} \ No newline at end of file diff --git a/src/main/resources/i18n/Scalars_de.properties b/src/main/resources/i18n/Scalars_de.properties index cbe1dee82d..de929f4770 100644 --- a/src/main/resources/i18n/Scalars_de.properties +++ b/src/main/resources/i18n/Scalars_de.properties @@ -13,20 +13,20 @@ # Prior to Java 9, properties files are encoded in ISO-8859-1. # We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss # -Scalar.unexpectedAstType=Erwartet wurde ein AST-Typ von ''{0}'', aber es war ein ''{1}'' +Scalar.unexpectedAstType=Erwartet wurde ein AST type von ''{0}'', aber es war ein ''{1}'' # Enum.badInput=Ung\u00fcltige Eingabe f\u00fcr enum ''{0}''. Unbekannter Wert ''{1}'' Enum.badName=Ung\u00fcltige Eingabe f\u00fcr enum ''{0}''. Kein Wert f\u00fcr den Namen ''{1}'' gefunden -Enum.unallowableValue=Literaler Wert nicht in den zul\u00e4ssigen Werten f\u00fcr enum ''{0}'' - ''{1}'' +Enum.unallowableValue=Literal nicht in den zul\u00e4ssigen Werten f\u00fcr enum ''{0}'' - ''{1}'' # Int.notInt=Erwartet wurde ein Wert, der in den Typ ''Int'' konvertiert werden kann, aber es war ein ''{0}'' Int.outsideRange=Erwarteter Wert im Integer-Bereich, aber es war ein ''{0}'' # ID.notId=Erwartet wurde ein Wert, der in den Typ ''ID'' umgewandelt werden kann, aber es war ein ''{0}'' -ID.unexpectedAstType=Erwartet wurde ein AST-Typ von ''IntValue'' oder ''StringValue'', aber es war ein ''{0}'' +ID.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''StringValue'', aber es war ein ''{0}'' # Float.notFloat=Erwartet wurde ein Wert, der in den Typ ''Float'' konvertiert werden kann, aber es war ein ''{0}'' -Float.unexpectedAstType=Erwartet wurde ein AST-Typ von ''IntValue'' oder ''FloatValue'', aber es war ein ''{0}'' +Float.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''FloatValue'', aber es war ein ''{0}'' # Boolean.notBoolean=Erwartet wurde ein Wert, der in den Typ ''Boolean'' konvertiert werden kann, aber es war ein ''{0}'' -Boolean.unexpectedAstType=Erwartet wurde ein AST-Typ ''BooleanValue'', aber es war ein ''{0}'' +Boolean.unexpectedAstType=Erwartet wurde ein AST type ''BooleanValue'', aber es war ein ''{0}'' diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 1ab468df7a..728fe4d99d 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -13,21 +13,21 @@ # Prior to Java 9, properties files are encoded in ISO-8859-1. # We have to use \u00fc instead of the German ue character, \u00e4 for ae, \u00f6 for oe, \u00df for ss # -ExecutableDefinitions.notExecutableType=Validierungsfehler ({0}) : Die Typdefinition ''{1}'' ist nicht ausf\u00fchrbar -ExecutableDefinitions.notExecutableSchema=Validierungsfehler ({0}) : Die Schemadefinition ist nicht ausf\u00fchrbar -ExecutableDefinitions.notExecutableDirective=Validierungsfehler ({0}) : Die Direktivendefinition ''{1}'' ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableType=Validierungsfehler ({0}) : Type definition ''{1}'' ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableSchema=Validierungsfehler ({0}) : Schema definition ist nicht ausf\u00fchrbar +ExecutableDefinitions.notExecutableDirective=Validierungsfehler ({0}) : Directive definition ''{1}'' ist nicht ausf\u00fchrbar ExecutableDefinitions.notExecutableDefinition=Validierungsfehler ({0}) : Die angegebene Definition ist nicht ausf\u00fchrbar # FieldsOnCorrectType.unknownField=Validierungsfehler ({0}) : Feld ''{1}'' vom Typ ''{2}'' ist nicht definiert # -FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Die Bedingung f\u00fcr den Inline-Fragmenttyp ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Union stehen -FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Die Bedingung des Fragmenttyps ist ung\u00fcltig, muss auf Objekt/Schnittstelle/Union stehen +FragmentsOnCompositeType.invalidInlineTypeCondition=Validierungsfehler ({0}) : Inline fragment type condition ist ung\u00fcltig, muss auf Object/Interface/Union stehen +FragmentsOnCompositeType.invalidFragmentTypeCondition=Validierungsfehler ({0}) : Fragment type condition ist ung\u00fcltig, muss auf Object/Interface/Union stehen # -KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes Direktivenargument ''{1}'' -KnownArgumentNames.unknownFieldArg=Validierungsfehler ({0}) : Unbekanntes Feldargument ''{1}'' +KnownArgumentNames.unknownDirectiveArg=Validierungsfehler ({0}) : Unbekanntes directive argument ''{1}'' +KnownArgumentNames.unknownFieldArg=Validierungsfehler ({0}) : Unbekanntes field argument ''{1}'' # -KnownDirectives.unknownDirective=Validierungsfehler ({0}) : Unbekannte Direktive ''{1}'' -KnownDirectives.directiveNotAllowed=Validierungsfehler ({0}) : Direktive ''{1}'' ist hier nicht erlaubt +KnownDirectives.unknownDirective=Validierungsfehler ({0}) : Unbekannte directive ''{1}'' +KnownDirectives.directiveNotAllowed=Validierungsfehler ({0}) : Directive ''{1}'' ist hier nicht erlaubt # KnownFragmentNames.undefinedFragment=Validierungsfehler ({0}) : Undefiniertes Fragment ''{1}'' # @@ -36,7 +36,7 @@ KnownTypeNames.unknownType=Validierungsfehler ({0}) : Unbekannter Typ ''{1}'' LoneAnonymousOperation.withOthers=Validierungsfehler ({0}) : Anonyme Operation mit anderen Operationen LoneAnonymousOperation.namedOperation=Validierungsfehler ({0}) : Operation ''{1}'' folgt der anonymen Operation # -NoFragmentCycles.cyclesNotAllowed=Validierungsfehler ({0}) : Fragmentzyklen nicht erlaubt +NoFragmentCycles.cyclesNotAllowed=Validierungsfehler ({0}) : Fragment cycles nicht erlaubt # NoUndefinedVariables.undefinedVariable=Validierungsfehler ({0}) : Undefinierte Variable ''{1}'' # @@ -46,28 +46,28 @@ NoUnusedVariables.unusedVariable=Validierungsfehler ({0}) : Unbenutzte Variable # OverlappingFieldsCanBeMerged.differentFields=Validierungsfehler ({0}) : ''{1}'' : ''{2}'' und ''{3}'' sind unterschiedliche Felder OverlappingFieldsCanBeMerged.differentArgs=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Argumente -OverlappingFieldsCanBeMerged.differentNullability=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Nullbarkeitsformen -OverlappingFieldsCanBeMerged.differentLists=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche Listenformen +OverlappingFieldsCanBeMerged.differentNullability=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche nullability shapes +OverlappingFieldsCanBeMerged.differentLists=Validierungsfehler ({0}) : ''{1}'' : Felder haben unterschiedliche list shapes OverlappingFieldsCanBeMerged.differentReturnTypes=Validierungsfehler ({0}) : ''{1}'' : gibt verschiedene Typen ''{2}'' und ''{3}'' zur\u00fcck # -PossibleFragmentSpreads.inlineIncompatibleTypes=Validierungsfehler ({0}) : Fragment kann hier nicht verbreitet werden, da Objekte vom Typ ''{1}'' niemals vom Typ ''{2}'' sein k\u00f6nnen -PossibleFragmentSpreads.fragmentIncompatibleTypes=Validierungsfehler ({0}) : Fragment ''{1}'' kann hier nicht verbreitet werden, da Objekte vom Typ ''{2}'' niemals vom Typ ''{3}'' sein k\u00f6nnen +PossibleFragmentSpreads.inlineIncompatibleTypes=Validierungsfehler ({0}) : Fragment kann hier nicht verbreitet werden, da object vom Typ ''{1}'' niemals vom Typ ''{2}'' sein k\u00f6nnen +PossibleFragmentSpreads.fragmentIncompatibleTypes=Validierungsfehler ({0}) : Fragment ''{1}'' kann hier nicht verbreitet werden, da object vom Typ ''{2}'' niemals vom Typ ''{3}'' sein k\u00f6nnen # -ProvidedNonNullArguments.missingFieldArg=Validierungsfehler ({0}) : Fehlendes Feldargument ''{1}'' -ProvidedNonNullArguments.missingDirectiveArg=Validierungsfehler ({0}) : Fehlendes Direktivenargument ''{1}'' -ProvidedNonNullArguments.nullValue=Validierungsfehler ({0}) : Nullwert f\u00fcr Nicht-Null-Feldargument ''{1}'' +ProvidedNonNullArguments.missingFieldArg=Validierungsfehler ({0}) : Fehlendes field argument ''{1}'' +ProvidedNonNullArguments.missingDirectiveArg=Validierungsfehler ({0}) : Fehlendes directive argument ''{1}'' +ProvidedNonNullArguments.nullValue=Validierungsfehler ({0}) : Nullwert f\u00fcr non-null field argument ''{1}'' # ScalarLeaves.subselectionOnLeaf=Validierungsfehler ({0}) : Unterauswahl f\u00fcr Blatttyp ''{1}'' von Feld ''{2}'' nicht zul\u00e4ssig ScalarLeaves.subselectionRequired=Validierungsfehler ({0}) : Unterauswahl erforderlich f\u00fcr Typ ''{1}'' des Feldes ''{2}'' # -SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld haben -SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' muss genau ein Stammfeld mit Fragmenten haben -SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Stammfeld ''{2}'' kann kein Introspektionsfeld sein -SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Die Abonnementoperation ''{1}'' Fragmentstammfeld ''{2}'' kann kein Introspektionsfeld sein +SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field mit Fragmenten haben +SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Subscription operation ''{1}'' root field ''{2}'' kann kein introspection field sein +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' kann kein introspection field sein # UniqueArgumentNames.uniqueArgument=Validierungsfehler ({0}) : Es kann nur ein Argument namens ''{1}'' geben # -UniqueDirectiveNamesPerLocation.uniqueDirectives=Validierungsfehler ({0}) : Nicht wiederholbare Direktive m\u00fcssen innerhalb einer Lokation eindeutig benannt werden. Die Direktive ''{1}'', die auf einem ''{2}'' verwendet wird, ist nicht eindeutig +UniqueDirectiveNamesPerLocation.uniqueDirectives=Validierungsfehler ({0}) : Nicht wiederholbare directive m\u00fcssen innerhalb einer Lokation eindeutig benannt werden. Directive ''{1}'', die auf einem ''{2}'' verwendet wird, ist nicht eindeutig # UniqueFragmentNames.oneFragment=Validierungsfehler ({0}) : Es kann nur ein Fragment namens ''{1}'' geben # @@ -94,7 +94,7 @@ ArgumentValidationUtil.handleEnumError=Validierungsfehler ({0}) : Argument ''{1} # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleEnumErrorCustomMessage=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' ist kein g\u00fcltiges ''{3}'' - {4} # suppress inspection "UnusedProperty" -ArgumentValidationUtil.handleNotObjectError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' muss ein Objekttyp sein +ArgumentValidationUtil.handleNotObjectError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' muss ein object type sein # suppress inspection "UnusedProperty" ArgumentValidationUtil.handleMissingFieldsError=Validierungsfehler ({0}) : Argument ''{1}'' mit Wert ''{2}'' fehlen Pflichtfelder ''{3}'' # suppress inspection "UnusedProperty" From 2e8e1c408e22d3098bdebaf2e457def7c94e3fb8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 21 Oct 2022 16:16:06 +1100 Subject: [PATCH 187/294] Update test --- src/test/groovy/graphql/i18n/I18nTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/i18n/I18nTest.groovy b/src/test/groovy/graphql/i18n/I18nTest.groovy index ceac9e88ea..ab02791572 100644 --- a/src/test/groovy/graphql/i18n/I18nTest.groovy +++ b/src/test/groovy/graphql/i18n/I18nTest.groovy @@ -31,7 +31,7 @@ class I18nTest extends Specification { when: def message = i18n.msg("ExecutableDefinitions.notExecutableType") then: - message == "Validierungsfehler ({0}) : Die Typdefinition '{1}' ist nicht ausführbar" + message == "Validierungsfehler ({0}) : Type definition '{1}' ist nicht ausführbar" } static def assertBundleStaticShape(ResourceBundle bundle) { From e5287d633ff8da12cc483e4349ede75071ce30e5 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 23 Oct 2022 17:05:56 +1100 Subject: [PATCH 188/294] Fix typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 486da839ab..41f2c19b01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thanks for contributing to graphql-java! Please be sure that you read the [Code of Conduct](CODE_OF_CONDUCT.md) before contributing to this project and please -create a new Issue and discuss first what your are planning to do for bigger changes. +create a new Issue and discuss first what you are planning to do for larger changes. The overall goal of graphql-java is to have a correct implementation of the [GraphQL Spec](https://github.com/facebook/graphql/) in a production ready way. From c7ae4dda251f0c301a659ef96687ab95e8864cc3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 24 Oct 2022 14:35:37 +1000 Subject: [PATCH 189/294] work on higher level schema diff --- .../ana/EditOperationAnalysisResult.java | 38 +- .../diffing/ana/EditOperationAnalyzer.java | 150 ++-- .../schema/diffing/ana/InterfaceModified.java | 54 -- .../schema/diffing/ana/ObjectModified.java | 134 ---- .../schema/diffing/ana/SchemaChange.java | 26 - .../schema/diffing/ana/SchemaChanges.java | 178 ----- .../schema/diffing/ana/SchemaDifference.java | 658 ++++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 58 +- 8 files changed, 787 insertions(+), 509 deletions(-) delete mode 100644 src/main/java/graphql/schema/diffing/ana/InterfaceModified.java delete mode 100644 src/main/java/graphql/schema/diffing/ana/ObjectModified.java delete mode 100644 src/main/java/graphql/schema/diffing/ana/SchemaChange.java delete mode 100644 src/main/java/graphql/schema/diffing/ana/SchemaChanges.java create mode 100644 src/main/java/graphql/schema/diffing/ana/SchemaDifference.java diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java index 826de33737..3221301099 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -6,19 +6,19 @@ @ExperimentalApi public class EditOperationAnalysisResult { - private final Map objectChanges; - private final Map interfaceChanges; - private final Map unionChanges; - private final Map enumChanges; - private final Map inputObjectChanges; - private final Map scalarChanges; - - public EditOperationAnalysisResult(Map objectChanges, - Map interfaceChanges, - Map unionChanges, - Map enumChanges, - Map inputObjectChanges, - Map scalarChanges) { + private final Map objectChanges; + private final Map interfaceChanges; + private final Map unionChanges; + private final Map enumChanges; + private final Map inputObjectChanges; + private final Map scalarChanges; + + public EditOperationAnalysisResult(Map objectChanges, + Map interfaceChanges, + Map unionChanges, + Map enumChanges, + Map inputObjectChanges, + Map scalarChanges) { this.objectChanges = objectChanges; this.interfaceChanges = interfaceChanges; this.unionChanges = unionChanges; @@ -27,27 +27,27 @@ public EditOperationAnalysisResult(Map object this.scalarChanges = scalarChanges; } - public Map getObjectChanges() { + public Map getObjectChanges() { return objectChanges; } - public Map getInterfaceChanges() { + public Map getInterfaceChanges() { return interfaceChanges; } - public Map getUnionChanges() { + public Map getUnionChanges() { return unionChanges; } - public Map getEnumChanges() { + public Map getEnumChanges() { return enumChanges; } - public Map getInputObjectChanges() { + public Map getInputObjectChanges() { return inputObjectChanges; } - public Map getScalarChanges() { + public Map getScalarChanges() { return scalarChanges; } } diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 67dc7bec66..1f6f92c19b 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -9,13 +9,25 @@ import graphql.schema.diffing.Vertex; import graphql.schema.idl.ScalarInfo; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.ana.SchemaChanges.*; +import static graphql.schema.diffing.ana.SchemaDifference.*; +import static graphql.schema.diffing.ana.SchemaDifference.EnumDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceModification; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectDifference; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectModification; +import static graphql.schema.diffing.ana.SchemaDifference.ScalarDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.ScalarDifference; +import static graphql.schema.diffing.ana.SchemaDifference.UnionDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.UnionDifference; /** * Higher level GraphQL semantic assigned to @@ -30,12 +42,12 @@ public class EditOperationAnalyzer { // private List changes = new ArrayList<>(); - private Map objectChanges = new LinkedHashMap<>(); - private Map interfaceChanges = new LinkedHashMap<>(); - private Map unionChanges = new LinkedHashMap<>(); - private Map enumChanges = new LinkedHashMap<>(); - private Map inputObjectChanges = new LinkedHashMap<>(); - private Map scalarChanges = new LinkedHashMap<>(); + private Map objectDifferences = new LinkedHashMap<>(); + private Map interfaceDifferences = new LinkedHashMap<>(); + private Map unionDifferences = new LinkedHashMap<>(); + private Map enumDifferences = new LinkedHashMap<>(); + private Map inputObjectDifferences = new LinkedHashMap<>(); + private Map scalarDifferences = new LinkedHashMap<>(); public EditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, @@ -52,7 +64,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleTypeVertexChanges(editOperations); handleEdgeChanges(editOperations); handleOtherChanges(editOperations); - return new EditOperationAnalysisResult(objectChanges, interfaceChanges, unionChanges, enumChanges, inputObjectChanges, scalarChanges); + return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); } private void handleOtherChanges(List editOperations) { @@ -81,10 +93,10 @@ private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { - ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); + ObjectModification objectModification = getObjectModification(fieldsContainerForField.getName()); String oldName = editOperation.getSourceVertex().getName(); String newName = field.getName(); - objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldRenamed(oldName, newName)); + objectModification.getDetails().add(new ObjectFieldRename(oldName, newName)); } } @@ -95,9 +107,9 @@ private void fieldAdded(EditOperation editOperation) { if (isNewObject(fieldsContainerForField.getName())) { return; } - ObjectModified objectModified = getObjectModified(fieldsContainerForField.getName()); + ObjectModification objectModification = getObjectModification(fieldsContainerForField.getName()); String name = field.getName(); - objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldAdded(name)); + objectModification.getDetails().add(new ObjectFieldAddition(name)); } } @@ -112,7 +124,7 @@ private void handleTypeVertexChanges(List editOperations) { deletedTypeVertex(editOperation); break; case CHANGE_VERTEX: - modifiedTypeVertex(editOperation); + ModificationTypeVertex(editOperation); break; } } @@ -165,13 +177,13 @@ private void deletedTypeVertex(EditOperation editOperation) { } } - private void modifiedTypeVertex(EditOperation editOperation) { + private void ModificationTypeVertex(EditOperation editOperation) { switch (editOperation.getTargetVertex().getType()) { case SchemaGraph.OBJECT: - modifiedObject(editOperation); + ModificationObject(editOperation); break; case SchemaGraph.INTERFACE: - modifiedInterface(editOperation); + ModificationInterface(editOperation); break; // case SchemaGraph.UNION: // changedUnion(editOperation); @@ -229,11 +241,11 @@ private void typeEdgeChanged(EditOperation editOperation) { Vertex container = newSchemaGraph.getFieldsContainerForField(field); if (container.isOfType(SchemaGraph.OBJECT)) { Vertex object = container; - ObjectModified objectModified = getObjectModified(object.getName()); + ObjectModification objectModification = getObjectModification(object.getName()); String fieldName = field.getName(); String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); - objectModified.getObjectModifiedDetails().add(new ObjectModified.FieldTypeModified(fieldName, oldType, newType)); + objectModification.getDetails().add(new ObjectFieldTypeModification(fieldName, oldType, newType)); } } } @@ -255,8 +267,8 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { } Vertex objectVertex = newEdge.getFrom(); Vertex interfaceVertex = newEdge.getTo(); - ObjectModified.AddedInterfaceToObjectDetail addedInterfaceToObjectDetail = new ObjectModified.AddedInterfaceToObjectDetail(interfaceVertex.getName()); - getObjectModified(objectVertex.getName()).getObjectModifiedDetails().add(addedInterfaceToObjectDetail); + ObjectInterfaceImplementationAddition objectInterfaceImplementationAddition = new ObjectInterfaceImplementationAddition(interfaceVertex.getName()); + getObjectModification(objectVertex.getName()).getDetails().add(objectInterfaceImplementationAddition); } else if (from.isOfType(SchemaGraph.INTERFACE)) { if (isNewInterface(from.getName())) { @@ -264,8 +276,8 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { } Vertex interfaceFromVertex = newEdge.getFrom(); Vertex interfaceVertex = newEdge.getTo(); - InterfaceModified.AddedInterfaceToInterfaceDetail addedInterfaceToObjectDetail = new InterfaceModified.AddedInterfaceToInterfaceDetail(interfaceVertex.getName()); - getInterfaceModified(interfaceFromVertex.getName()).getInterfaceChangeDetails().add(addedInterfaceToObjectDetail); + InterfaceInterfaceImplementationAddition addition = new InterfaceInterfaceImplementationAddition(interfaceVertex.getName()); + getInterfaceModification(interfaceFromVertex.getName()).getDetails().add(addition); } else { Assert.assertShouldNeverHappen("expected an implementation edge"); } @@ -273,66 +285,66 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { } private boolean isNewObject(String name) { - return objectChanges.containsKey(name) && objectChanges.get(name) instanceof ObjectAdded; + return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectAddition; } - private boolean isRemovedObject(String name) { - return objectChanges.containsKey(name) && objectChanges.get(name) instanceof ObjectRemoved; + private boolean isDeletionObject(String name) { + return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectDeletion; } private boolean isNewInterface(String name) { - return interfaceChanges.containsKey(name) && interfaceChanges.get(name) instanceof InterfaceAdded; + return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceAddition; } - private ObjectModified getObjectModified(String newName) { - if (!objectChanges.containsKey(newName)) { - objectChanges.put(newName, new ObjectModified(newName)); + private ObjectModification getObjectModification(String newName) { + if (!objectDifferences.containsKey(newName)) { + objectDifferences.put(newName, new ObjectModification(newName)); } - assertTrue(objectChanges.get(newName) instanceof ObjectModified); - return (ObjectModified) objectChanges.get(newName); + assertTrue(objectDifferences.get(newName) instanceof ObjectModification); + return (ObjectModification) objectDifferences.get(newName); } - private InterfaceModified getInterfaceModified(String newName) { - if (!interfaceChanges.containsKey(newName)) { - interfaceChanges.put(newName, new InterfaceModified(newName)); + private InterfaceModification getInterfaceModification(String newName) { + if (!interfaceDifferences.containsKey(newName)) { + interfaceDifferences.put(newName, new InterfaceModification(newName)); } - assertTrue(interfaceChanges.get(newName) instanceof InterfaceModified); - return (InterfaceModified) interfaceChanges.get(newName); + assertTrue(interfaceDifferences.get(newName) instanceof InterfaceModification); + return (InterfaceModification) interfaceDifferences.get(newName); } private void addedObject(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); - ObjectAdded objectAdded = new ObjectAdded(objectName); - objectChanges.put(objectName, objectAdded); + ObjectAddition objectAddition = new ObjectAddition(objectName); + objectDifferences.put(objectName, objectAddition); } private void addedInterface(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); - InterfaceAdded interfacedAdded = new InterfaceAdded(objectName); - interfaceChanges.put(objectName, interfacedAdded); + InterfaceAddition interfaceAddition = new InterfaceAddition(objectName); + interfaceDifferences.put(objectName, interfaceAddition); } private void addedUnion(EditOperation editOperation) { String unionName = editOperation.getTargetVertex().getName(); - UnionAdded unionAdded = new UnionAdded(unionName); - unionChanges.put(unionName, unionAdded); + UnionAddition addition = new UnionAddition(unionName); + unionDifferences.put(unionName, addition); } private void addedInputObject(EditOperation editOperation) { String inputObjectName = editOperation.getTargetVertex().getName(); - InputObjectAdded added = new InputObjectAdded(inputObjectName); - inputObjectChanges.put(inputObjectName, added); + InputObjectAddition addition = new InputObjectAddition(inputObjectName); + inputObjectDifferences.put(inputObjectName, addition); } private void addedEnum(EditOperation editOperation) { String enumName = editOperation.getTargetVertex().getName(); - EnumAdded enumAdded = new EnumAdded(enumName); - enumChanges.put(enumName, enumAdded); + EnumAddition enumAddition = new EnumAddition(enumName); + enumDifferences.put(enumName, enumAddition); } private void addedScalar(EditOperation editOperation) { @@ -343,51 +355,51 @@ private void addedScalar(EditOperation editOperation) { return; } - ScalarAdded scalarAdded = new ScalarAdded(scalarName); - scalarChanges.put(scalarName, scalarAdded); + ScalarAddition addition = new ScalarAddition(scalarName); + scalarDifferences.put(scalarName, addition); } private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); - ObjectRemoved change = new ObjectRemoved(objectName); - objectChanges.put(objectName, change); + ObjectDeletion change = new ObjectDeletion(objectName); + objectDifferences.put(objectName, change); } private void removedInterface(EditOperation editOperation) { String interfaceName = editOperation.getSourceVertex().getName(); - InterfaceRemoved change = new InterfaceRemoved(interfaceName); - interfaceChanges.put(interfaceName, change); + InterfaceDeletion change = new InterfaceDeletion(interfaceName); + interfaceDifferences.put(interfaceName, change); } private void removedUnion(EditOperation editOperation) { String unionName = editOperation.getSourceVertex().getName(); - UnionRemoved change = new UnionRemoved(unionName); - unionChanges.put(unionName, change); + UnionDeletion change = new UnionDeletion(unionName); + unionDifferences.put(unionName, change); } private void removedInputObject(EditOperation editOperation) { String name = editOperation.getSourceVertex().getName(); - InputObjectRemoved change = new InputObjectRemoved(name); - inputObjectChanges.put(name, change); + InputObjectDeletion change = new InputObjectDeletion(name); + inputObjectDifferences.put(name, change); } private void removedEnum(EditOperation editOperation) { String enumName = editOperation.getSourceVertex().getName(); - EnumRemoved change = new EnumRemoved(enumName); - enumChanges.put(enumName, change); + EnumDeletion deletion = new EnumDeletion(enumName); + enumDifferences.put(enumName, deletion); } private void removedScalar(EditOperation editOperation) { String scalarName = editOperation.getSourceVertex().getName(); - ScalarRemoved change = new ScalarRemoved(scalarName); - scalarChanges.put(scalarName, change); + ScalarDeletion change = new ScalarDeletion(scalarName); + scalarDifferences.put(scalarName, change); } private void removedArgument(EditOperation editOperation) { @@ -398,25 +410,25 @@ private void removedArgument(EditOperation editOperation) { Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { Vertex object = fieldsContainerForField; - getObjectModified(object.getName()).getObjectModifiedDetails().add(new ObjectModified.ArgumentRemoved(field.getName(), removedArgument.getName())); + getObjectModification(object.getName()).getDetails().add(new ObjectFieldArgumentDeletion(field.getName(), removedArgument.getName())); } } } - private void modifiedObject(EditOperation editOperation) { + private void ModificationObject(EditOperation editOperation) { // // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); // - ObjectModified objectModified = new ObjectModified(objectName); - objectChanges.put(objectName, objectModified); + ObjectModification objectModification = new ObjectModification(objectName); + objectDifferences.put(objectName, objectModification); } - private void modifiedInterface(EditOperation editOperation) { + private void ModificationInterface(EditOperation editOperation) { String interfaceName = editOperation.getTargetVertex().getName(); - InterfaceModified interfaceModified = new InterfaceModified(interfaceName); + InterfaceModification interfaceModification = new InterfaceModification(interfaceName); // we store the modification against the new name - interfaceChanges.put(interfaceName, interfaceModified); + interfaceDifferences.put(interfaceName, interfaceModification); } // // private void changedUnion(EditOperation editOperation) { @@ -456,7 +468,7 @@ private void modifiedInterface(EditOperation editOperation) { // Vertex field = editOperation.getTargetVertex(); // Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); // -// FieldModified objectAdded = new FieldModified(field.getName(), fieldsContainerForField.getName()); +// FieldModification objectAdded = new FieldModification(field.getName(), fieldsContainerForField.getName()); // changes.add(objectAdded); // } // diff --git a/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java b/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java deleted file mode 100644 index 77b164221e..0000000000 --- a/src/main/java/graphql/schema/diffing/ana/InterfaceModified.java +++ /dev/null @@ -1,54 +0,0 @@ -package graphql.schema.diffing.ana; - -import graphql.ExperimentalApi; - -import java.util.ArrayList; -import java.util.List; - -@ExperimentalApi -public class InterfaceModified implements SchemaChange.InterfaceChange { - private final String name; - - private final List interfaceChangeDetails = new ArrayList<>(); - - interface InterfaceChangeDetail { - - } - - public static class AddedInterfaceToInterfaceDetail implements InterfaceChangeDetail { - private final String name; - - public AddedInterfaceToInterfaceDetail(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - public static class RemovedInterfaceFromInterfaceDetail implements InterfaceChangeDetail { - private final String name; - - public RemovedInterfaceFromInterfaceDetail(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - - public InterfaceModified(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // this will be mutated - public List getInterfaceChangeDetails() { - return interfaceChangeDetails; - } -} diff --git a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java b/src/main/java/graphql/schema/diffing/ana/ObjectModified.java deleted file mode 100644 index e1749c0362..0000000000 --- a/src/main/java/graphql/schema/diffing/ana/ObjectModified.java +++ /dev/null @@ -1,134 +0,0 @@ -package graphql.schema.diffing.ana; - -import graphql.ExperimentalApi; -import graphql.util.FpKit; - -import java.util.ArrayList; -import java.util.List; - -@ExperimentalApi -public class ObjectModified implements SchemaChange.ObjectChange { - private final String name; - - private final List objectModifiedDetails = new ArrayList<>(); - - public interface ObjectModifiedDetails { - - } - - public static class AddedInterfaceToObjectDetail implements ObjectModifiedDetails { - private final String name; - - public AddedInterfaceToObjectDetail(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class RemovedInterfaceToObjectDetail implements ObjectModifiedDetails { - private final String name; - - public RemovedInterfaceToObjectDetail(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class FieldRenamed implements ObjectModifiedDetails { - private final String oldName; - private final String newName; - - public FieldRenamed(String oldName, String newName) { - this.oldName = oldName; - this.newName = newName; - } - - public String getNewName() { - return newName; - } - - public String getOldName() { - return oldName; - } - } - - public static class FieldAdded implements ObjectModifiedDetails { - private final String name; - - public FieldAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class FieldTypeModified implements ObjectModifiedDetails { - private final String name; - private final String oldType; - private final String newType; - - public FieldTypeModified(String name, String oldType, String newType) { - this.name = name; - this.oldType = oldType; - this.newType = newType; - } - - public String getName() { - return name; - } - - public String getOldType() { - return oldType; - } - - public String getNewType() { - return newType; - } - } - - public static class ArgumentRemoved implements ObjectModifiedDetails { - - private final String fieldName; - private final String argumentName; - - public ArgumentRemoved(String fieldName, String argumentName) { - this.fieldName = fieldName; - this.argumentName = argumentName; - } - - public String getFieldName() { - return fieldName; - } - - public String getArgumentName() { - return argumentName; - } - } - - - public ObjectModified(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // this will be mutated - - public List getObjectModifiedDetails() { - return objectModifiedDetails; - } - - public List getObjectModifiedDetails(Class clazz) { - return (List) FpKit.filterList(objectModifiedDetails, clazz::isInstance); - } -} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java b/src/main/java/graphql/schema/diffing/ana/SchemaChange.java deleted file mode 100644 index 329a37b264..0000000000 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChange.java +++ /dev/null @@ -1,26 +0,0 @@ -package graphql.schema.diffing.ana; - -import graphql.ExperimentalApi; - -@ExperimentalApi -public interface SchemaChange { - interface ObjectChange extends SchemaChange { - - } - interface InterfaceChange extends SchemaChange { - - } - interface UnionChange extends SchemaChange { - - } - interface EnumChange extends SchemaChange { - - } - interface InputObjectChange extends SchemaChange { - - } - interface ScalarChange extends SchemaChange { - - } - -} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java b/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java deleted file mode 100644 index 83b3fc861f..0000000000 --- a/src/main/java/graphql/schema/diffing/ana/SchemaChanges.java +++ /dev/null @@ -1,178 +0,0 @@ -package graphql.schema.diffing.ana; - -import graphql.ExperimentalApi; - -@ExperimentalApi -public class SchemaChanges { - - /** - * Type means Object, Interface, Union, InputObject, Scalar, Enum - */ - public static class ObjectAdded implements SchemaChange.ObjectChange { - private String name; - - public ObjectAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class InterfaceAdded implements SchemaChange.InterfaceChange { - private String name; - - public InterfaceAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class ScalarAdded implements SchemaChange.ScalarChange { - private String name; - - public ScalarAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class UnionAdded implements SchemaChange.UnionChange { - private String name; - - public UnionAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class InputObjectAdded implements SchemaChange.InputObjectChange { - private String name; - - public InputObjectAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class EnumAdded implements SchemaChange.EnumChange { - private String name; - - public EnumAdded(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class FieldAdded implements SchemaChange { - - private final String name; - private final String fieldsContainer; - - public FieldAdded(String name, String fieldsContainer) { - this.name = name; - this.fieldsContainer = fieldsContainer; - } - - public String getName() { - return name; - } - - public String getFieldsContainer() { - return fieldsContainer; - } - } - - public static class FieldModified implements SchemaChange { - - private final String name; - private final String fieldsContainer; - - public FieldModified(String name, String fieldsContainer) { - this.name = name; - this.fieldsContainer = fieldsContainer; - } - - public String getName() { - return name; - } - - - public String getFieldsContainer() { - return fieldsContainer; - } - - } - - - public static class ObjectRemoved implements SchemaChange.ObjectChange { - private String name; - - public ObjectRemoved(String name) { - this.name = name; - } - - } - - public static class InterfaceRemoved implements SchemaChange.InterfaceChange { - private String name; - - public InterfaceRemoved(String name) { - this.name = name; - } - - } - - public static class UnionRemoved implements SchemaChange.UnionChange { - private String name; - - public UnionRemoved(String name) { - this.name = name; - } - - } - - public static class ScalarRemoved implements SchemaChange.ScalarChange { - private String name; - - public ScalarRemoved(String name) { - this.name = name; - } - - } - - public static class InputObjectRemoved implements SchemaChange.InputObjectChange { - private String name; - - public InputObjectRemoved(String name) { - this.name = name; - } - - } - public static class EnumRemoved implements SchemaChange.EnumChange { - private String name; - - public EnumRemoved(String name) { - this.name = name; - } - - } - - -} diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java new file mode 100644 index 0000000000..3621279815 --- /dev/null +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -0,0 +1,658 @@ +package graphql.schema.diffing.ana; + +import graphql.ExperimentalApi; +import graphql.util.FpKit; + +import java.util.ArrayList; +import java.util.List; + +/** + * Any kind of difference between two schemas is a SchemaDifference. + * + * Below that we have three different possible kind of differences: + * - Addition + * - Deletion + * - Modification + */ +@ExperimentalApi +public interface SchemaDifference { + + interface SchemaAddition extends SchemaDifference { + + } + + interface SchemaDeletion extends SchemaDifference { + + } + + interface SchemaModification extends SchemaDifference { + + } + + interface SchemaModificationDetail extends SchemaDifference { + + } + + //------------ Object + public + interface ObjectDifference extends SchemaDifference { + + } + + class ObjectAddition implements SchemaAddition, ObjectDifference { + private final String name; + + public ObjectAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectDeletion implements SchemaDeletion, ObjectDifference { + private final String name; + + public ObjectDeletion(String name) { + this.name = name; + } + } + + class ObjectModification implements SchemaModification, ObjectDifference { + private final String oldName; + private final String newName; + private final List details = new ArrayList<>(); + + public ObjectModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public ObjectModification(String newName) { + this.oldName = ""; + this.newName = newName; + } + + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + } + + interface ObjectModificationDetail { + + } + + class ObjectInterfaceImplementationAddition implements ObjectModificationDetail { + private final String name; + + public ObjectInterfaceImplementationAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectInterfaceImplementationDeletion implements ObjectModificationDetail { + private final String name; + + public ObjectInterfaceImplementationDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectFieldAddition implements ObjectModificationDetail { + private final String name; + + public ObjectFieldAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectFieldDeletion implements ObjectModificationDetail { + private final String name; + + public ObjectFieldDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectFieldRename implements ObjectModificationDetail { + private final String oldName; + private final String newName; + + public ObjectFieldRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + class ObjectFieldTypeModification implements ObjectModificationDetail { + private final String fieldName; + private final String oldType; + private final String newType; + + public ObjectFieldTypeModification(String fieldName, String oldType, String newType) { + this.fieldName = fieldName; + this.oldType = oldType; + this.newType = newType; + } + + public String getNewType() { + return newType; + } + + public String getOldType() { + return oldType; + } + } + + class ObjectFieldArgumentDeletion implements ObjectModificationDetail { + private final String fieldName; + private final String name; + + public ObjectFieldArgumentDeletion(String fieldName, String name) { + this.fieldName = fieldName; + this.name = name; + } + + public String getName() { + return name; + } + + public String getFieldName() { + return fieldName; + } + } + + class ObjectFieldArgumentAddition implements ObjectModificationDetail { + private final String name; + + public ObjectFieldArgumentAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ObjectFieldArgumentTypeModification implements ObjectModificationDetail { + private final String oldType; + private final String newType; + + public ObjectFieldArgumentTypeModification(String oldType, String newType) { + this.oldType = oldType; + this.newType = newType; + } + + public String getNewType() { + return newType; + } + + public String getOldType() { + return oldType; + } + } + + class ObjectFieldArgumentDefaultValueModification { + private final String oldValue; + private final String newValue; + + public ObjectFieldArgumentDefaultValueModification(String oldValue, String newValue) { + this.oldValue = oldValue; + this.newValue = newValue; + } + + public String getOldValue() { + return oldValue; + } + + public String getNewValue() { + return newValue; + } + } + + //------------ Interface + interface InterfaceDifference extends SchemaDifference { + + } + + class InterfaceAddition implements SchemaAddition, InterfaceDifference { + private final String name; + + public InterfaceAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceDeletion implements SchemaDeletion, InterfaceDifference { + private final String name; + + public InterfaceDeletion(String name) { + this.name = name; + } + } + + class InterfaceModification implements SchemaModification, InterfaceDifference { + private final String oldName; + private final String newName; + private final List details = new ArrayList<>(); + + public InterfaceModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public InterfaceModification(String newName) { + this.oldName = ""; + this.newName = newName; + } + + public List getDetails() { + return details; + } + + public List getInterfaceModifiedDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + } + + interface InterfaceModificationDetail { + + } + + class InterfaceInterfaceImplementationAddition implements InterfaceModificationDetail { + private final String name; + + public InterfaceInterfaceImplementationAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceInterfaceImplementationDeletion implements InterfaceModificationDetail { + private final String name; + + public InterfaceInterfaceImplementationDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceFieldAddition implements InterfaceModificationDetail { + private final String name; + + public InterfaceFieldAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceFieldDeletion implements InterfaceModificationDetail { + private final String name; + + public InterfaceFieldDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceFieldTypeModification implements InterfaceModificationDetail { + private final String oldType; + private final String newType; + + public InterfaceFieldTypeModification(String oldType, String newType) { + this.oldType = oldType; + this.newType = newType; + } + + public String getNewType() { + return newType; + } + + public String getOldType() { + return oldType; + } + } + + class InterfaceFieldArgumentDeletion implements InterfaceModificationDetail { + private final String name; + + public InterfaceFieldArgumentDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceFieldArgumentAddition implements InterfaceModificationDetail { + private final String name; + + public InterfaceFieldArgumentAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InterfaceFieldArgumentTypeModification implements InterfaceModificationDetail { + private final String oldType; + private final String newType; + + public InterfaceFieldArgumentTypeModification(String oldType, String newType) { + this.oldType = oldType; + this.newType = newType; + } + + public String getNewType() { + return newType; + } + + public String getOldType() { + return oldType; + } + } + + class InterfaceFieldArgumentDefaultValueModification { + private final String oldValue; + private final String newValue; + + public InterfaceFieldArgumentDefaultValueModification(String oldValue, String newValue) { + this.oldValue = oldValue; + this.newValue = newValue; + } + + public String getOldValue() { + return oldValue; + } + + public String getNewValue() { + return newValue; + } + } + + + // -----Union----------- + interface UnionDifference extends SchemaDifference { + + } + + class UnionAddition implements SchemaAddition, UnionDifference { + private final String name; + + public UnionAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class UnionDeletion implements SchemaDeletion, UnionDifference { + private final String name; + + public UnionDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class UnionModification implements SchemaModification, UnionDifference { + private final String oldName; + private final String newName; + + public UnionModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + + //--------InputObject + + interface InputObjectDifference extends SchemaDifference { + + } + + class InputObjectAddition implements SchemaAddition, InputObjectDifference { + private final String name; + + public InputObjectAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InputObjectDeletion implements SchemaDeletion, InputObjectDifference { + private final String name; + + public InputObjectDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class InputObjectModification implements SchemaModification, InputObjectDifference { + private final String oldName; + private final String newName; + + public InputObjectModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + + //-------Enum + interface EnumDifference extends SchemaDifference { + + } + + class EnumAddition implements SchemaAddition, EnumDifference { + private final String name; + + public EnumAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class EnumDeletion implements SchemaDeletion, EnumDifference { + private final String name; + + public EnumDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class EnumModification implements SchemaModification, EnumDifference { + private final String oldName; + private final String newName; + + public EnumModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + + //--------Scalar + interface ScalarDifference extends SchemaDifference { + + } + + class ScalarAddition implements SchemaAddition, ScalarDifference { + private final String name; + + public ScalarAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ScalarDeletion implements SchemaDeletion, ScalarDifference { + private final String name; + + public ScalarDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ScalarModification implements SchemaModification, ScalarDifference { + private final String oldName; + private final String newName; + + public ScalarModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + + //------Directive + interface DirectiveDifference extends SchemaDifference { + + } + + class DirectiveAddition implements SchemaAddition, DirectiveDifference { + private final String name; + + public DirectiveAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class DirectiveDeletion implements SchemaDeletion, DirectiveDifference { + private final String name; + + public DirectiveDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class DirectiveModification implements SchemaModification, SchemaDifference { + private final String oldName; + private final String newName; + + public DirectiveModification(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + + +} diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index f47b469472..4927b34bba 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -4,7 +4,7 @@ import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification -import static graphql.schema.diffing.ana.SchemaChanges.* +import static graphql.schema.diffing.ana.SchemaDifference.* class EditOperationAnalyzerTest extends Specification { @@ -23,9 +23,9 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModified - def objectModified = changes.objectChanges["Query"] as ObjectModified - def fieldRenames = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.FieldRenamed }) as List + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def fieldRenames = objectModification.getDetails(ObjectFieldRename.class) fieldRenames[0].oldName == "hello" fieldRenames[0].newName == "hello2" } @@ -45,11 +45,11 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModified - def objectModified = changes.objectChanges["Query"] as ObjectModified - def typeModified = objectModified.getObjectModifiedDetails(ObjectModified.FieldTypeModified.class); - typeModified[0].oldType == "String" - typeModified[0].newType == "String!" + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def typeModification = objectModification.getDetails(ObjectFieldTypeModification.class) + typeModification[0].oldType == "String" + typeModification[0].newType == "String!" } def "argument removed"() { @@ -67,11 +67,11 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModified - def objectModified = changes.objectChanges["Query"] as ObjectModified - def argumentRemoved = objectModified.getObjectModifiedDetails(ObjectModified.ArgumentRemoved.class); + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def argumentRemoved = objectModification.getDetails(ObjectFieldArgumentDeletion.class); argumentRemoved[0].fieldName == "hello" - argumentRemoved[0].argumentName == "arg" + argumentRemoved[0].name == "arg" } def "field added"() { @@ -91,9 +91,9 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModified - def objectModified = changes.objectChanges["Query"] as ObjectModified - def fieldAdded = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.FieldAdded }) as List + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def fieldAdded = objectModification.getDetails(ObjectFieldAddition) fieldAdded[0].name == "newOne" } @@ -116,7 +116,7 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Foo"] instanceof ObjectAdded + changes.objectChanges["Foo"] instanceof ObjectAddition } def "new Interface introduced"() { @@ -144,11 +144,11 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.interfaceChanges.size() == 1 - changes.interfaceChanges["Node"] instanceof InterfaceAdded + changes.interfaceChanges["Node"] instanceof InterfaceAddition changes.objectChanges.size() == 1 - changes.objectChanges["Foo"] instanceof ObjectModified - def objectModified = changes.objectChanges["Foo"] as ObjectModified - def addedInterfaceDetails = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + changes.objectChanges["Foo"] instanceof ObjectModification + def objectModification = changes.objectChanges["Foo"] as ObjectModification + def addedInterfaceDetails = objectModification.getDetails(ObjectInterfaceImplementationAddition.class) addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" } @@ -175,9 +175,9 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.interfaceChanges.size() == 1 - changes.interfaceChanges["Node"] instanceof InterfaceAdded + changes.interfaceChanges["Node"] instanceof InterfaceAddition changes.objectChanges.size() == 1 - changes.objectChanges["Foo"] instanceof ObjectAdded + changes.objectChanges["Foo"] instanceof ObjectAddition } def "interfaced renamed"() { @@ -208,7 +208,7 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.interfaceChanges.size() == 1 - changes.interfaceChanges["Node2"] instanceof InterfaceModified + changes.interfaceChanges["Node2"] instanceof InterfaceModification } def "interfaced renamed and another interface added to it"() { @@ -243,12 +243,12 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.interfaceChanges.size() == 2 - changes.interfaceChanges["Node2"] instanceof InterfaceModified - changes.interfaceChanges["NewI"] instanceof InterfaceAdded + changes.interfaceChanges["Node2"] instanceof InterfaceModification + changes.interfaceChanges["NewI"] instanceof InterfaceAddition changes.objectChanges.size() == 1 - changes.objectChanges["Foo"] instanceof ObjectModified - def objectModified = changes.objectChanges["Foo"] as ObjectModified - def addedInterfaceDetails = objectModified.objectModifiedDetails.findAll({ it instanceof ObjectModified.AddedInterfaceToObjectDetail }) as List + changes.objectChanges["Foo"] instanceof ObjectModification + def objectModification = changes.objectChanges["Foo"] as ObjectModification + def addedInterfaceDetails = objectModification.getDetails(ObjectInterfaceImplementationAddition) addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "NewI" From dcb648b8aaade7922e7484b4493a39e31132c81b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 24 Oct 2022 17:46:25 +1000 Subject: [PATCH 190/294] work on higher level schema diff --- .../schema/diffing/SchemaGraphFactory.java | 4 +- .../diffing/ana/EditOperationAnalyzer.java | 75 +++++++++++++++---- .../schema/diffing/ana/SchemaDifference.java | 35 +++++++-- .../ana/EditOperationAnalyzerTest.groovy | 40 ++++++++++ 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 74b70ab064..d07a20460b 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -158,7 +158,7 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i Edge typeEdge = new Edge(inputFieldVertex, typeVertex); String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); if (inputField.hasSetDefaultValue()) { - typeEdgeLabel += ";defaultValue='" + AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType(), GraphQLContext.getDefault(), Locale.getDefault())) + "'"; + typeEdgeLabel += ";defaultValue=" + AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType(), GraphQLContext.getDefault(), Locale.getDefault())); } typeEdge.setLabel(typeEdgeLabel); @@ -234,7 +234,7 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume Edge typeEdge = new Edge(argumentVertex, typeVertex); String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); if (graphQLArgument.hasSetDefaultValue()) { - typeEdgeLabel += ";defaultValue='" + AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault())) + "'"; + typeEdgeLabel += ";defaultValue=" + AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault())); } typeEdge.setLabel(typeEdgeLabel); schemaGraph.addEdge(typeEdge); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 1f6f92c19b..af64e9e0fd 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -124,7 +124,7 @@ private void handleTypeVertexChanges(List editOperations) { deletedTypeVertex(editOperation); break; case CHANGE_VERTEX: - ModificationTypeVertex(editOperation); + changedTypeVertex(editOperation); break; } } @@ -177,13 +177,13 @@ private void deletedTypeVertex(EditOperation editOperation) { } } - private void ModificationTypeVertex(EditOperation editOperation) { + private void changedTypeVertex(EditOperation editOperation) { switch (editOperation.getTargetVertex().getType()) { case SchemaGraph.OBJECT: - ModificationObject(editOperation); + changedObject(editOperation); break; case SchemaGraph.INTERFACE: - ModificationInterface(editOperation); + changedInterface(editOperation); break; // case SchemaGraph.UNION: // changedUnion(editOperation); @@ -235,19 +235,55 @@ private void typeEdgeChanged(EditOperation editOperation) { Edge targetEdge = editOperation.getTargetEdge(); Vertex from = targetEdge.getFrom(); Vertex to = targetEdge.getTo(); - // edge goes from FIELD to the TYPE if (from.isOfType(SchemaGraph.FIELD)) { - Vertex field = from; - Vertex container = newSchemaGraph.getFieldsContainerForField(field); - if (container.isOfType(SchemaGraph.OBJECT)) { - Vertex object = container; - ObjectModification objectModification = getObjectModification(object.getName()); - String fieldName = field.getName(); - String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); - String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); - objectModification.getDetails().add(new ObjectFieldTypeModification(fieldName, oldType, newType)); + fieldTypeChanged(editOperation); + } else if (from.isOfType(SchemaGraph.ARGUMENT)) { + argumentTypeChanged(editOperation); + } + } + + private void argumentTypeChanged(EditOperation editOperation) { + Edge targetEdge = editOperation.getTargetEdge(); + Vertex argument = targetEdge.getFrom(); + Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + Vertex field = fieldOrDirective; + Vertex objectOrInterface = newSchemaGraph.getFieldsContainerForField(field); + String oldDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getSourceEdge()); + String newDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getTargetEdge()); + if (objectOrInterface.isOfType(SchemaGraph.OBJECT)) { + ObjectFieldArgumentDefaultValueModification defaultValueModification = new ObjectFieldArgumentDefaultValueModification( + field.getName(), + argument.getName(), + oldDefaultValue, + newDefaultValue); + getObjectModification(objectOrInterface.getName()).getDetails().add(defaultValueModification); + } else { + assertTrue(objectOrInterface.isOfType(SchemaGraph.INTERFACE)); + InterfaceFieldArgumentDefaultValueModification defaultValueModification = new InterfaceFieldArgumentDefaultValueModification( + field.getName(), + argument.getName(), + oldDefaultValue, + newDefaultValue); + getInterfaceModification(objectOrInterface.getName()).getDetails().add(defaultValueModification); } + + } + } + + private void fieldTypeChanged(EditOperation editOperation) { + Edge targetEdge = editOperation.getTargetEdge(); + Vertex field = targetEdge.getFrom(); + Vertex container = newSchemaGraph.getFieldsContainerForField(field); + if (container.isOfType(SchemaGraph.OBJECT)) { + Vertex object = container; + ObjectModification objectModification = getObjectModification(object.getName()); + String fieldName = field.getName(); + String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + objectModification.getDetails().add(new ObjectFieldTypeModification(fieldName, oldType, newType)); } + } // TODO: this is not great, we should avoid parsing the label like that @@ -258,6 +294,13 @@ private String getTypeFromEdgeLabel(Edge edge) { return type; } + private String getDefaultValueFromEdgeLabel(Edge edge) { + String label = edge.getLabel(); + assertTrue(label.startsWith("type=")); + String defaultValue = label.substring(label.indexOf(";defaultValue=") + ";defaultValue=".length()); + return defaultValue; + } + private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { Vertex from = newEdge.getFrom(); @@ -416,7 +459,7 @@ private void removedArgument(EditOperation editOperation) { } - private void ModificationObject(EditOperation editOperation) { + private void changedObject(EditOperation editOperation) { // // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); // @@ -424,7 +467,7 @@ private void ModificationObject(EditOperation editOperation) { objectDifferences.put(objectName, objectModification); } - private void ModificationInterface(EditOperation editOperation) { + private void changedInterface(EditOperation editOperation) { String interfaceName = editOperation.getTargetVertex().getName(); InterfaceModification interfaceModification = new InterfaceModification(interfaceName); // we store the modification against the new name diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 3621279815..70c8a7be13 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -220,11 +220,15 @@ public String getOldType() { } } - class ObjectFieldArgumentDefaultValueModification { + class ObjectFieldArgumentDefaultValueModification implements ObjectModificationDetail { + private final String fieldName; + private final String argumentName; private final String oldValue; private final String newValue; - public ObjectFieldArgumentDefaultValueModification(String oldValue, String newValue) { + public ObjectFieldArgumentDefaultValueModification(String fieldName, String argumentName, String oldValue, String newValue) { + this.fieldName = fieldName; + this.argumentName = argumentName; this.oldValue = oldValue; this.newValue = newValue; } @@ -236,6 +240,14 @@ public String getOldValue() { public String getNewValue() { return newValue; } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } } //------------ Interface @@ -282,7 +294,7 @@ public List getDetails() { return details; } - public List getInterfaceModifiedDetails(Class clazz) { + public List getDetails(Class clazz) { return (List) FpKit.filterList(details, clazz::isInstance); } } @@ -399,11 +411,16 @@ public String getOldType() { } } - class InterfaceFieldArgumentDefaultValueModification { + class InterfaceFieldArgumentDefaultValueModification implements InterfaceModificationDetail{ + private final String fieldName; + private final String argumentName; private final String oldValue; private final String newValue; - public InterfaceFieldArgumentDefaultValueModification(String oldValue, String newValue) { + + public InterfaceFieldArgumentDefaultValueModification(String fieldName, String argumentName, String oldValue, String newValue) { + this.fieldName = fieldName; + this.argumentName = argumentName; this.oldValue = oldValue; this.newValue = newValue; } @@ -415,6 +432,14 @@ public String getOldValue() { public String getNewValue() { return newValue; } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 4927b34bba..ff414e6c6a 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -74,6 +74,46 @@ class EditOperationAnalyzerTest extends Specification { argumentRemoved[0].name == "arg" } + def "argument default value modified for Object and Interface"() { + given: + def oldSdl = ''' + type Query implements Foo { + foo(arg: String = "bar"): String + } + interface Foo { + foo(arg: String = "bar"): String + } + + ''' + def newSdl = ''' + type Query implements Foo { + foo(arg: String = "barChanged"): String + } + interface Foo { + foo(arg: String = "barChanged"): String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + + then: + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def objDefaultValueModified = objectModification.getDetails(ObjectFieldArgumentDefaultValueModification.class); + objDefaultValueModified[0].fieldName == "foo" + objDefaultValueModified[0].argumentName == "arg" + objDefaultValueModified[0].oldValue == '"bar"' + objDefaultValueModified[0].newValue == '"barChanged"' + and: + changes.interfaceChanges["Foo"] instanceof InterfaceModification + def interfaceModification = changes.interfaceChanges["Foo"] as InterfaceModification + def intDefaultValueModified = interfaceModification.getDetails(InterfaceFieldArgumentDefaultValueModification.class); + intDefaultValueModified[0].fieldName == "foo" + intDefaultValueModified[0].argumentName == "arg" + intDefaultValueModified[0].oldValue == '"bar"' + intDefaultValueModified[0].newValue == '"barChanged"' + } + def "field added"() { given: def oldSdl = ''' From 9324a2297a16ccaca6e5935f5fec6fd3e47076f9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 24 Oct 2022 19:43:15 +1000 Subject: [PATCH 191/294] work on higher level schema diff --- .../graphql/schema/diffing/SchemaDiffing.java | 2 +- .../diffing/ana/EditOperationAnalyzer.java | 128 +++++++++++++----- .../schema/diffing/ana/SchemaDifference.java | 4 + .../ana/EditOperationAnalyzerTest.groovy | 36 ++++- 4 files changed, 135 insertions(+), 35 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index fa24beccda..f8d56f77cd 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -32,7 +32,7 @@ public EditOperationAnalysisResult diffAndAnalyze(GraphQLSchema graphQLSchema1, targetGraph = new SchemaGraphFactory("target-").createGraph(graphQLSchema2); DiffImpl.OptimalEdit optimalEdit = diffImpl(sourceGraph, targetGraph); EditOperationAnalyzer editOperationAnalyzer = new EditOperationAnalyzer(graphQLSchema1, graphQLSchema1, sourceGraph, targetGraph); - return editOperationAnalyzer.analyzeEdits(optimalEdit.listOfEditOperations.get(0)); + return editOperationAnalyzer.analyzeEdits(optimalEdit.listOfEditOperations.get(0),optimalEdit.mappings.get(0)); } public DiffImpl.OptimalEdit diffGraphQLSchemaAllEdits(GraphQLSchema graphQLSchema1, GraphQLSchema graphQLSchema2) throws Exception { diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index af64e9e0fd..ae1f45bc39 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -5,6 +5,7 @@ import graphql.schema.GraphQLSchema; import graphql.schema.diffing.Edge; import graphql.schema.diffing.EditOperation; +import graphql.schema.diffing.Mapping; import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; import graphql.schema.idl.ScalarInfo; @@ -60,14 +61,8 @@ public EditOperationAnalyzer(GraphQLSchema oldSchema, this.newSchemaGraph = newSchemaGraph; } - public EditOperationAnalysisResult analyzeEdits(List editOperations) { + public EditOperationAnalysisResult analyzeEdits(List editOperations, Mapping mapping) { handleTypeVertexChanges(editOperations); - handleEdgeChanges(editOperations); - handleOtherChanges(editOperations); - return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); - } - - private void handleOtherChanges(List editOperations) { for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { case CHANGE_VERTEX: @@ -86,9 +81,44 @@ private void handleOtherChanges(List editOperations) { } } } + handleTypeChanges(editOperations, mapping); + handleImplementsChanges(editOperations, mapping); + + return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); + } + private void handleTypeChanges(List editOperations, Mapping mapping) { + for (EditOperation editOperation : editOperations) { + Edge newEdge = editOperation.getTargetEdge(); + switch (editOperation.getOperation()) { + case INSERT_EDGE: + if (newEdge.getLabel().startsWith("type=")) { + typeEdgeInserted(editOperation, editOperations, mapping); + } + break; + case CHANGE_EDGE: + if (newEdge.getLabel().startsWith("type=")) { + typeEdgeChanged(editOperation); + } + break; + } + } } + private void handleImplementsChanges(List editOperations, Mapping mapping) { + for (EditOperation editOperation : editOperations) { + Edge newEdge = editOperation.getTargetEdge(); + switch (editOperation.getOperation()) { + case INSERT_EDGE: + if (newEdge.getLabel().startsWith("implements ")) { + newInterfaceAddedToInterfaceOrObject(newEdge); + } + break; + } + } + } + + private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); @@ -155,7 +185,7 @@ private void insertedTypeVertex(EditOperation editOperation) { } private void deletedTypeVertex(EditOperation editOperation) { - switch (editOperation.getTargetVertex().getType()) { + switch (editOperation.getSourceVertex().getType()) { case SchemaGraph.OBJECT: removedObject(editOperation); break; @@ -201,36 +231,61 @@ private void changedTypeVertex(EditOperation editOperation) { } - private void handleEdgeChanges(List editOperations) { - for (EditOperation editOperation : editOperations) { - switch (editOperation.getOperation()) { - case INSERT_EDGE: - insertedEdge(editOperation); - break; -// case DELETE_EDGE: -// deletedEdge(editOperation); -// break; - case CHANGE_EDGE: - changedEdge(editOperation); - break; - } + + private void typeEdgeInserted(EditOperation editOperation, List editOperations, Mapping mapping) { + Edge newEdge = editOperation.getTargetEdge(); + Vertex from = newEdge.getFrom(); + if (from.isOfType(SchemaGraph.FIELD)) { + typeEdgeInsertedForField(editOperation, editOperations, mapping); } + } - private void insertedEdge(EditOperation editOperation) { - Edge newEdge = editOperation.getTargetEdge(); - if (newEdge.getLabel().startsWith("implements ")) { - newInterfaceAddedToInterfaceOrObject(newEdge); + private void typeEdgeInsertedForField(EditOperation editOperation, List editOperations, Mapping mapping) { + Vertex field = editOperation.getTargetEdge().getFrom(); + Vertex objectOrInterface = newSchemaGraph.getFieldsContainerForField(field); + if (objectOrInterface.isOfType(SchemaGraph.OBJECT)) { + Vertex object = objectOrInterface; + // if the whole object is new we are done + if (isNewObject(object.getName())) { + return; + } + // if the field is new, we are done too + if (isNewFieldForExistingObject(object.getName(), field.getName())) { + return; + } + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + // this means we have an existing object changed its type + // and there must be a deleted edge with the old type information + EditOperation deletedTypeEdgeOperation = findDeletedEdge(field, editOperations, mapping); + String oldType = getTypeFromEdgeLabel(deletedTypeEdgeOperation.getSourceEdge()); + ObjectFieldTypeModification objectFieldTypeModification = new ObjectFieldTypeModification(field.getName(), oldType, newType); + getObjectModification(object.getName()).getDetails().add(objectFieldTypeModification); + + } else { + assertTrue(objectOrInterface.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = objectOrInterface; + if (isNewInterface(interfaze.getName())) { + return; + } } } - private void changedEdge(EditOperation editOperation) { - Edge newEdge = editOperation.getTargetEdge(); - if (newEdge.getLabel().startsWith("type=")) { - typeEdgeChanged(editOperation); + + private EditOperation findDeletedEdge(Vertex targetVertexFrom, List editOperations, Mapping mapping) { + Vertex sourceVertexFrom = mapping.getSource(targetVertexFrom); + for (EditOperation editOperation : editOperations) { + if (editOperation.getOperation() == EditOperation.Operation.DELETE_EDGE) { + Edge deletedEdge = editOperation.getSourceEdge(); + if (deletedEdge.getFrom() == sourceVertexFrom) { + return editOperation; + } + } } + return Assert.assertShouldNeverHappen(); } + private void typeEdgeChanged(EditOperation editOperation) { Edge targetEdge = editOperation.getTargetEdge(); Vertex from = targetEdge.getFrom(); @@ -331,6 +386,18 @@ private boolean isNewObject(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectAddition; } + private boolean isNewFieldForExistingObject(String objectName, String fieldName) { + if (!objectDifferences.containsKey(objectName)) { + return false; + } + if (!(objectDifferences.get(objectName) instanceof ObjectModification)) { + return false; + } + ObjectModification objectModification = (ObjectModification) objectDifferences.get(objectName); + List newFields = objectModification.getDetails(ObjectFieldAddition.class); + return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } + private boolean isDeletionObject(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectDeletion; } @@ -460,9 +527,8 @@ private void removedArgument(EditOperation editOperation) { } private void changedObject(EditOperation editOperation) { -// // object changes include: adding/removing Interface, adding/removing applied directives, changing name String objectName = editOperation.getTargetVertex().getName(); -// + ObjectModification objectModification = new ObjectModification(objectName); objectDifferences.put(objectName, objectModification); } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 70c8a7be13..e5309a520d 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -57,6 +57,10 @@ class ObjectDeletion implements SchemaDeletion, ObjectDifference { public ObjectDeletion(String name) { this.name = name; } + + public String getName() { + return name; + } } class ObjectModification implements SchemaModification, ObjectDifference { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ff414e6c6a..ba23a450d5 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -128,7 +128,6 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - when: def changes = changes(oldSdl, newSdl) then: changes.objectChanges["Query"] instanceof ObjectModification @@ -137,7 +136,7 @@ class EditOperationAnalyzerTest extends Specification { fieldAdded[0].name == "newOne" } - def "Object added"() { + def "object added"() { given: def oldSdl = ''' type Query { @@ -159,6 +158,35 @@ class EditOperationAnalyzerTest extends Specification { changes.objectChanges["Foo"] instanceof ObjectAddition } + def "object removed and field type changed"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + type Foo { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Foo"] instanceof ObjectDeletion + (changes.objectChanges["Foo"] as ObjectDeletion).name == "Foo" + changes.objectChanges["Query"] instanceof ObjectModification + def queryObjectModification = changes.objectChanges["Query"] as ObjectModification + queryObjectModification.details.size() == 1 + queryObjectModification.details[0] instanceof ObjectFieldTypeModification + (queryObjectModification.details[0] as ObjectFieldTypeModification).oldType == "Foo" + (queryObjectModification.details[0] as ObjectFieldTypeModification).newType == "String" + + } + def "new Interface introduced"() { given: def oldSdl = ''' @@ -216,8 +244,10 @@ class EditOperationAnalyzerTest extends Specification { then: changes.interfaceChanges.size() == 1 changes.interfaceChanges["Node"] instanceof InterfaceAddition - changes.objectChanges.size() == 1 + changes.objectChanges.size() == 2 changes.objectChanges["Foo"] instanceof ObjectAddition + changes.objectChanges["Query"] instanceof ObjectModification + (changes.objectChanges["Query"] as ObjectModification).getDetails()[0] instanceof ObjectFieldTypeModification } def "interfaced renamed"() { From 97f93cac5e58e0dd88a2eb7265337f1cac62fcb6 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 10:14:24 +1000 Subject: [PATCH 192/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 37 ++++++++ .../schema/diffing/ana/SchemaDifference.java | 94 ++++++++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 93 ++++++++++++++++++ 3 files changed, 219 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ae1f45bc39..36b5882cf3 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -9,6 +9,7 @@ import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; import graphql.schema.idl.ScalarInfo; +import graphql.util.EscapeUtil; import java.util.LinkedHashMap; import java.util.List; @@ -83,6 +84,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio } handleTypeChanges(editOperations, mapping); handleImplementsChanges(editOperations, mapping); + handleUnionMemberChanges(editOperations,mapping); return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); } @@ -105,6 +107,19 @@ private void handleTypeChanges(List editOperations, Mapping mappi } } + private void handleUnionMemberChanges(List editOperations, Mapping mapping) { + for (EditOperation editOperation : editOperations) { + Edge newEdge = editOperation.getTargetEdge(); + switch (editOperation.getOperation()) { + case INSERT_EDGE: + if (newEdge.getFrom().isOfType(SchemaGraph.UNION)) { + handleUnionMemberAdded(editOperation); + } + break; + } + } + } + private void handleImplementsChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { Edge newEdge = editOperation.getTargetEdge(); @@ -118,6 +133,17 @@ private void handleImplementsChanges(List editOperations, Mapping } } + private void handleUnionMemberAdded(EditOperation editOperation) { + Edge newEdge = editOperation.getTargetEdge(); + Vertex union = newEdge.getFrom(); + if(isNewUnion(union.getName())) { + return; + } + Vertex newMemberObject = newEdge.getTo(); + UnionModification unionModification = getUnionModification(union.getName()); + unionModification.getDetails().add(new UnionMemberAddition(newMemberObject.getName())); + } + private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); @@ -386,6 +412,10 @@ private boolean isNewObject(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectAddition; } + private boolean isNewUnion(String name) { + return unionDifferences.containsKey(name) && unionDifferences.get(name) instanceof UnionAddition; + } + private boolean isNewFieldForExistingObject(String objectName, String fieldName) { if (!objectDifferences.containsKey(objectName)) { return false; @@ -413,6 +443,13 @@ private ObjectModification getObjectModification(String newName) { assertTrue(objectDifferences.get(newName) instanceof ObjectModification); return (ObjectModification) objectDifferences.get(newName); } + private UnionModification getUnionModification(String newName) { + if (!unionDifferences.containsKey(newName)) { + unionDifferences.put(newName, new UnionModification(newName)); + } + assertTrue(unionDifferences.get(newName) instanceof UnionModification); + return (UnionModification) unionDifferences.get(newName); + } private InterfaceModification getInterfaceModification(String newName) { if (!interfaceDifferences.containsKey(newName)) { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index e5309a520d..707e23f0d5 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -115,7 +115,7 @@ public String getName() { } } - class ObjectFieldAddition implements ObjectModificationDetail { + class ObjectFieldAddition implements ObjectModificationDetail { private final String name; public ObjectFieldAddition(String name) { @@ -127,7 +127,7 @@ public String getName() { } } - class ObjectFieldDeletion implements ObjectModificationDetail { + class ObjectFieldDeletion implements ObjectModificationDetail { private final String name; public ObjectFieldDeletion(String name) { @@ -156,6 +156,7 @@ public String getOldName() { return oldName; } } + class ObjectFieldTypeModification implements ObjectModificationDetail { private final String fieldName; private final String oldType; @@ -331,7 +332,7 @@ public String getName() { } } - class InterfaceFieldAddition implements InterfaceModificationDetail { + class InterfaceFieldAddition implements InterfaceModificationDetail { private final String name; public InterfaceFieldAddition(String name) { @@ -343,7 +344,7 @@ public String getName() { } } - class InterfaceFieldDeletion implements InterfaceModificationDetail { + class InterfaceFieldDeletion implements InterfaceModificationDetail { private final String name; public InterfaceFieldDeletion(String name) { @@ -415,7 +416,7 @@ public String getOldType() { } } - class InterfaceFieldArgumentDefaultValueModification implements InterfaceModificationDetail{ + class InterfaceFieldArgumentDefaultValueModification implements InterfaceModificationDetail { private final String fieldName; private final String argumentName; private final String oldValue; @@ -480,11 +481,18 @@ class UnionModification implements SchemaModification, UnionDifference { private final String oldName; private final String newName; + private final List details = new ArrayList<>(); + public UnionModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; } + public UnionModification(String newName) { + this.oldName = ""; + this.newName = newName; + } + public String getNewName() { return newName; } @@ -492,6 +500,43 @@ public String getNewName() { public String getOldName() { return oldName; } + + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + + } + + interface UnionModificationDetail { + + } + + class UnionMemberAddition implements UnionModificationDetail { + private final String name; + + public UnionMemberAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class UnionMemberDeletion implements UnionModificationDetail { + private final String name; + + public UnionMemberDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } } //--------InputObject @@ -575,6 +620,8 @@ class EnumModification implements SchemaModification, EnumDifference { private final String oldName; private final String newName; + private final List details = new ArrayList<>(); + public EnumModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; @@ -587,6 +634,43 @@ public String getNewName() { public String getOldName() { return oldName; } + + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + + } + + interface EnumModificationDetail { + + } + + class EnumMemberDeletion implements EnumModificationDetail { + private final String name; + + public EnumMemberDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class EnumMemberAddition implements EnumModificationDetail { + private final String name; + + public EnumMemberAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } } //--------Scalar diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ba23a450d5..f2dc18b4a5 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -30,6 +30,99 @@ class EditOperationAnalyzerTest extends Specification { fieldRenames[0].newName == "hello2" } + def "union added"() { + given: + def oldSdl = ''' + type Query { + hello: String + } + ''' + def newSdl = ''' + type Query { + hello: String + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionAddition + (changes.unionChanges["U"] as UnionAddition).name == "U" + } + + def "union removed"() { + given: + def oldSdl = ''' + type Query { + hello: String + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + def newSdl = ''' + type Query { + hello: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionDeletion + (changes.unionChanges["U"] as UnionDeletion).name == "U" + } + + def "union member added"() { + given: + def oldSdl = ''' + type Query { + hello: String + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + def newSdl = ''' + type Query { + hello: String + u: U + } + union U = A | B | C + type A { + foo: String + } + type B { + foo: String + } + type C { + foo: String + } + + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionModification + } + def "field type modified"() { given: def oldSdl = ''' From 0c1cd4faba9aa13e7b381dc04258ca1755b7900b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 10:20:23 +1000 Subject: [PATCH 193/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 51 +++++++++++++------ .../ana/EditOperationAnalyzerTest.groovy | 35 +++++++++++++ 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 36b5882cf3..fce2ca630a 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -9,7 +9,6 @@ import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; import graphql.schema.idl.ScalarInfo; -import graphql.util.EscapeUtil; import java.util.LinkedHashMap; import java.util.List; @@ -84,7 +83,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio } handleTypeChanges(editOperations, mapping); handleImplementsChanges(editOperations, mapping); - handleUnionMemberChanges(editOperations,mapping); + handleUnionMemberChanges(editOperations, mapping); return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); } @@ -109,13 +108,19 @@ private void handleTypeChanges(List editOperations, Mapping mappi private void handleUnionMemberChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { - Edge newEdge = editOperation.getTargetEdge(); switch (editOperation.getOperation()) { case INSERT_EDGE: + Edge newEdge = editOperation.getTargetEdge(); if (newEdge.getFrom().isOfType(SchemaGraph.UNION)) { handleUnionMemberAdded(editOperation); } break; + case DELETE_EDGE: + Edge oldEdge = editOperation.getSourceEdge(); + if (oldEdge.getFrom().isOfType(SchemaGraph.UNION)) { + handleUnionMemberDeleted(editOperation); + } + break; } } } @@ -136,7 +141,7 @@ private void handleImplementsChanges(List editOperations, Mapping private void handleUnionMemberAdded(EditOperation editOperation) { Edge newEdge = editOperation.getTargetEdge(); Vertex union = newEdge.getFrom(); - if(isNewUnion(union.getName())) { + if (isUnionAdded(union.getName())) { return; } Vertex newMemberObject = newEdge.getTo(); @@ -144,6 +149,17 @@ private void handleUnionMemberAdded(EditOperation editOperation) { unionModification.getDetails().add(new UnionMemberAddition(newMemberObject.getName())); } + private void handleUnionMemberDeleted(EditOperation editOperation) { + Edge deletedEdge = editOperation.getSourceEdge(); + Vertex union = deletedEdge.getFrom(); + if (isUnionDeleted(union.getName())) { + return; + } + Vertex memberObject = deletedEdge.getTo(); + UnionModification unionModification = getUnionModification(union.getName()); + unionModification.getDetails().add(new UnionMemberDeletion(memberObject.getName())); + } + private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); @@ -160,7 +176,7 @@ private void fieldAdded(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { - if (isNewObject(fieldsContainerForField.getName())) { + if (isObjectAdded(fieldsContainerForField.getName())) { return; } ObjectModification objectModification = getObjectModification(fieldsContainerForField.getName()); @@ -273,11 +289,11 @@ private void typeEdgeInsertedForField(EditOperation editOperation, List detail.getName().equals(fieldName)); } - private boolean isDeletionObject(String name) { + private boolean isObjectDeleted(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectDeletion; } - private boolean isNewInterface(String name) { + private boolean isInterfaceAdded(String name) { return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceAddition; } @@ -443,6 +463,7 @@ private ObjectModification getObjectModification(String newName) { assertTrue(objectDifferences.get(newName) instanceof ObjectModification); return (ObjectModification) objectDifferences.get(newName); } + private UnionModification getUnionModification(String newName) { if (!unionDifferences.containsKey(newName)) { unionDifferences.put(newName, new UnionModification(newName)); diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index f2dc18b4a5..a36084ee62 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -121,6 +121,41 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: changes.unionChanges["U"] instanceof UnionModification + def unionModification = changes.unionChanges["U"] as UnionModification + unionModification.getDetails(UnionMemberAddition)[0].name == "C" + } + + def "union member deleted"() { + given: + def oldSdl = ''' + type Query { + hello: String + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + def newSdl = ''' + type Query { + hello: String + u: U + } + union U = A + type A { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionModification + def unionModification = changes.unionChanges["U"] as UnionModification + unionModification.getDetails(UnionMemberDeletion)[0].name == "B" } def "field type modified"() { From df04e36ccf71720ddd583a288a692e454c3715f3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 10:34:45 +1000 Subject: [PATCH 194/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 55 +++++++++++ .../schema/diffing/ana/SchemaDifference.java | 12 ++- .../ana/EditOperationAnalyzerTest.groovy | 97 +++++++++++++++++++ 3 files changed, 160 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index fce2ca630a..9dd1ca06a9 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -84,6 +84,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleTypeChanges(editOperations, mapping); handleImplementsChanges(editOperations, mapping); handleUnionMemberChanges(editOperations, mapping); + handleEnumValuesChanges(editOperations,mapping); return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); } @@ -124,6 +125,24 @@ private void handleUnionMemberChanges(List editOperations, Mappin } } } + private void handleEnumValuesChanges(List editOperations, Mapping mapping) { + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case INSERT_EDGE: + Edge newEdge = editOperation.getTargetEdge(); + if (newEdge.getFrom().isOfType(SchemaGraph.ENUM)) { + handleEnumValueAdded(editOperation); + } + break; + case DELETE_EDGE: + Edge oldEdge = editOperation.getSourceEdge(); + if (oldEdge.getFrom().isOfType(SchemaGraph.ENUM)) { + handleEnumValueDeleted(editOperation); + } + break; + } + } + } private void handleImplementsChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { @@ -160,6 +179,28 @@ private void handleUnionMemberDeleted(EditOperation editOperation) { unionModification.getDetails().add(new UnionMemberDeletion(memberObject.getName())); } + private void handleEnumValueAdded(EditOperation editOperation) { + Edge newEdge = editOperation.getTargetEdge(); + Vertex enumVertex = newEdge.getFrom(); + if (isEnumAdded(enumVertex.getName())) { + return; + } + Vertex newValue = newEdge.getTo(); + EnumModification enumModification = getEnumModification(enumVertex.getName()); + enumModification.getDetails().add(new EnumValueAddition(newValue.getName())); + } + + private void handleEnumValueDeleted(EditOperation editOperation) { + Edge deletedEdge = editOperation.getSourceEdge(); + Vertex enumVertex = deletedEdge.getFrom(); + if (isEnumDeleted(enumVertex.getName())) { + return; + } + Vertex value = deletedEdge.getTo(); + EnumModification enumModification = getEnumModification(enumVertex.getName()); + enumModification.getDetails().add(new EnumValueDeletion(value.getName())); + } + private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); @@ -435,6 +476,12 @@ private boolean isUnionAdded(String name) { private boolean isUnionDeleted(String name) { return unionDifferences.containsKey(name) && unionDifferences.get(name) instanceof UnionDeletion; } + private boolean isEnumDeleted(String name) { + return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumDeletion; + } + private boolean isEnumAdded(String name) { + return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumAddition; + } private boolean isFieldNewForExistingObject(String objectName, String fieldName) { if (!objectDifferences.containsKey(objectName)) { @@ -472,6 +519,14 @@ private UnionModification getUnionModification(String newName) { return (UnionModification) unionDifferences.get(newName); } + private EnumModification getEnumModification(String newName) { + if (!enumDifferences.containsKey(newName)) { + enumDifferences.put(newName, new EnumModification(newName)); + } + assertTrue(enumDifferences.get(newName) instanceof EnumModification); + return (EnumModification) enumDifferences.get(newName); + } + private InterfaceModification getInterfaceModification(String newName) { if (!interfaceDifferences.containsKey(newName)) { interfaceDifferences.put(newName, new InterfaceModification(newName)); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 707e23f0d5..6633a0df36 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -626,6 +626,10 @@ public EnumModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; } + public EnumModification(String newName) { + this.oldName = ""; + this.newName = newName; + } public String getNewName() { return newName; @@ -649,10 +653,10 @@ interface EnumModificationDetail { } - class EnumMemberDeletion implements EnumModificationDetail { + class EnumValueDeletion implements EnumModificationDetail { private final String name; - public EnumMemberDeletion(String name) { + public EnumValueDeletion(String name) { this.name = name; } @@ -661,10 +665,10 @@ public String getName() { } } - class EnumMemberAddition implements EnumModificationDetail { + class EnumValueAddition implements EnumModificationDetail { private final String name; - public EnumMemberAddition(String name) { + public EnumValueAddition(String name) { this.name = name; } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a36084ee62..5d8844cbec 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -452,6 +452,103 @@ class EditOperationAnalyzerTest extends Specification { } + def "enum added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo: E + } + enum E { + A, B + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.enumChanges["E"] instanceof EnumAddition + (changes.enumChanges["E"] as EnumAddition).getName() == "E" + } + + def "enum deleted"() { + given: + def oldSdl = ''' + type Query { + foo: E + } + enum E { + A, B + } + ''' + def newSdl = ''' + type Query { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.enumChanges["E"] instanceof EnumDeletion + (changes.enumChanges["E"] as EnumDeletion).getName() == "E" + } + + + + def "enum value added"() { + given: + def oldSdl = ''' + type Query { + e: E + } + enum E { + A + } + ''' + def newSdl = ''' + type Query { + e: E + } + enum E { + A, B + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.enumChanges["E"] instanceof EnumModification + def enumModification = changes.enumChanges["E"] as EnumModification + enumModification.getDetails(EnumValueAddition)[0].name == "B" + } + + def "enum value deleted"() { + given: + def oldSdl = ''' + type Query { + e: E + } + enum E { + A,B + } + ''' + def newSdl = ''' + type Query { + e: E + } + enum E { + A + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.enumChanges["E"] instanceof EnumModification + def enumModification = changes.enumChanges["E"] as EnumModification + enumModification.getDetails(EnumValueDeletion)[0].name == "B" + } EditOperationAnalysisResult changes( String oldSdl, From 6358d2642125615ed44badc8a3f82386f1a6e6c5 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 10:40:54 +1000 Subject: [PATCH 195/294] work on higher level schema diff --- .../ana/EditOperationAnalyzerTest.groovy | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5d8844cbec..6d325c047e 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -550,6 +550,90 @@ class EditOperationAnalyzerTest extends Specification { enumModification.getDetails(EnumValueDeletion)[0].name == "B" } + def "scalar added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo: E + } + scalar E + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.scalarChanges["E"] instanceof ScalarAddition + (changes.scalarChanges["E"] as ScalarAddition).getName() == "E" + } + + def "scalar deleted"() { + given: + def oldSdl = ''' + type Query { + foo: E + } + scalar E + ''' + def newSdl = ''' + type Query { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.scalarChanges["E"] instanceof ScalarDeletion + (changes.scalarChanges["E"] as ScalarDeletion).getName() == "E" + } + + def "input object added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.inputObjectChanges["I"] instanceof InputObjectAddition + (changes.inputObjectChanges["I"] as InputObjectAddition).getName() == "I" + } + + def "input object deleted"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.inputObjectChanges["I"] instanceof InputObjectDeletion + (changes.inputObjectChanges["I"] as InputObjectDeletion).getName() == "I" + } + EditOperationAnalysisResult changes( String oldSdl, String newSdl From 33bce7f09f709255b950eeff641cbd40176a88e7 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 10:50:13 +1000 Subject: [PATCH 196/294] work on higher level schema diff --- .../ana/EditOperationAnalysisResult.java | 10 ++++- .../diffing/ana/EditOperationAnalyzer.java | 36 ++++++++++++++-- .../ana/EditOperationAnalyzerTest.groovy | 42 +++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java index 3221301099..e1b9c754b0 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -13,18 +13,22 @@ public class EditOperationAnalysisResult { private final Map inputObjectChanges; private final Map scalarChanges; + private final Map directiveChanges; + public EditOperationAnalysisResult(Map objectChanges, Map interfaceChanges, Map unionChanges, Map enumChanges, Map inputObjectChanges, - Map scalarChanges) { + Map scalarChanges, + Map directiveChanges) { this.objectChanges = objectChanges; this.interfaceChanges = interfaceChanges; this.unionChanges = unionChanges; this.enumChanges = enumChanges; this.inputObjectChanges = inputObjectChanges; this.scalarChanges = scalarChanges; + this.directiveChanges = directiveChanges; } public Map getObjectChanges() { @@ -50,4 +54,8 @@ public Map getInputObjectChanges public Map getScalarChanges() { return scalarChanges; } + + public Map getDirectiveChanges() { + return directiveChanges; + } } diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 9dd1ca06a9..14b495d7b1 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -50,6 +50,8 @@ public class EditOperationAnalyzer { private Map inputObjectDifferences = new LinkedHashMap<>(); private Map scalarDifferences = new LinkedHashMap<>(); + private Map directiveDifferences = new LinkedHashMap<>(); + public EditOperationAnalyzer(GraphQLSchema oldSchema, GraphQLSchema newSchema, SchemaGraph oldSchemaGraph, @@ -86,7 +88,14 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleUnionMemberChanges(editOperations, mapping); handleEnumValuesChanges(editOperations,mapping); - return new EditOperationAnalysisResult(objectDifferences, interfaceDifferences, unionDifferences, enumDifferences, inputObjectDifferences, scalarDifferences); + return new EditOperationAnalysisResult( + objectDifferences, + interfaceDifferences, + unionDifferences, + enumDifferences, + inputObjectDifferences, + scalarDifferences, + directiveDifferences); } private void handleTypeChanges(List editOperations, Mapping mapping) { @@ -263,6 +272,9 @@ private void insertedTypeVertex(EditOperation editOperation) { case SchemaGraph.SCALAR: addedScalar(editOperation); break; + case SchemaGraph.DIRECTIVE: + addedDirective(editOperation); + break; } } @@ -285,7 +297,10 @@ private void deletedTypeVertex(EditOperation editOperation) { removedEnum(editOperation); break; case SchemaGraph.SCALAR: - removedScalar(editOperation); + deletedScalar(editOperation); + break; + case SchemaGraph.DIRECTIVE: + deletedDirective(editOperation); break; } } @@ -582,6 +597,14 @@ private void addedScalar(EditOperation editOperation) { scalarDifferences.put(scalarName, addition); } + private void addedDirective(EditOperation editOperation) { + String directiveName= editOperation.getTargetVertex().getName(); + + DirectiveAddition addition = new DirectiveAddition(directiveName); + directiveDifferences.put(directiveName, addition); + } + + private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); @@ -618,13 +641,20 @@ private void removedEnum(EditOperation editOperation) { enumDifferences.put(enumName, deletion); } - private void removedScalar(EditOperation editOperation) { + private void deletedScalar(EditOperation editOperation) { String scalarName = editOperation.getSourceVertex().getName(); ScalarDeletion change = new ScalarDeletion(scalarName); scalarDifferences.put(scalarName, change); } + private void deletedDirective(EditOperation editOperation) { + String directiveName = editOperation.getSourceVertex().getName(); + + DirectiveDeletion change = new DirectiveDeletion(directiveName); + directiveDifferences.put(directiveName, change); + } + private void removedArgument(EditOperation editOperation) { Vertex removedArgument = editOperation.getSourceVertex(); Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(removedArgument); diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 6d325c047e..8eed8af133 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -634,6 +634,48 @@ class EditOperationAnalyzerTest extends Specification { (changes.inputObjectChanges["I"] as InputObjectDeletion).getName() == "I" } + def "directive added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d on FIELD + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.directiveChanges["d"] instanceof DirectiveAddition + (changes.directiveChanges["d"] as DirectiveAddition).getName() == "d" + } + + def "directive deleted"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.directiveChanges["d"] instanceof DirectiveDeletion + (changes.directiveChanges["d"] as DirectiveDeletion).getName() == "d" + } + + + EditOperationAnalysisResult changes( String oldSdl, String newSdl From 573308d50ad328b54135e30833c6ee7874ba2c48 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 11:21:17 +1000 Subject: [PATCH 197/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 22 ++++-- .../schema/diffing/ana/SchemaDifference.java | 10 ++- .../ana/EditOperationAnalyzerTest.groovy | 69 ++++++++++++++++++- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 14b495d7b1..238456f532 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -86,7 +86,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleTypeChanges(editOperations, mapping); handleImplementsChanges(editOperations, mapping); handleUnionMemberChanges(editOperations, mapping); - handleEnumValuesChanges(editOperations,mapping); + handleEnumValuesChanges(editOperations, mapping); return new EditOperationAnalysisResult( objectDifferences, @@ -134,6 +134,7 @@ private void handleUnionMemberChanges(List editOperations, Mappin } } } + private void handleEnumValuesChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { @@ -313,9 +314,9 @@ private void changedTypeVertex(EditOperation editOperation) { case SchemaGraph.INTERFACE: changedInterface(editOperation); break; -// case SchemaGraph.UNION: -// changedUnion(editOperation); -// break; + case SchemaGraph.UNION: + changedUnion(editOperation); + break; // case SchemaGraph.INPUT_OBJECT: // changedInputObject(editOperation); // break; @@ -491,9 +492,11 @@ private boolean isUnionAdded(String name) { private boolean isUnionDeleted(String name) { return unionDifferences.containsKey(name) && unionDifferences.get(name) instanceof UnionDeletion; } + private boolean isEnumDeleted(String name) { return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumDeletion; } + private boolean isEnumAdded(String name) { return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumAddition; } @@ -598,14 +601,13 @@ private void addedScalar(EditOperation editOperation) { } private void addedDirective(EditOperation editOperation) { - String directiveName= editOperation.getTargetVertex().getName(); + String directiveName = editOperation.getTargetVertex().getName(); DirectiveAddition addition = new DirectiveAddition(directiveName); directiveDifferences.put(directiveName, addition); } - private void removedObject(EditOperation editOperation) { String objectName = editOperation.getSourceVertex().getName(); @@ -682,6 +684,14 @@ private void changedInterface(EditOperation editOperation) { // we store the modification against the new name interfaceDifferences.put(interfaceName, interfaceModification); } + + private void changedUnion(EditOperation editOperation) { + String newUnionName = editOperation.getTargetVertex().getName(); + String oldUnionName = editOperation.getSourceVertex().getName(); + + UnionModification objectModification = new UnionModification(oldUnionName, newUnionName); + unionDifferences.put(oldUnionName, objectModification); + } // // private void changedUnion(EditOperation editOperation) { // // object changes include: adding/removing Interface, adding/removing applied directives, changing name diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 6633a0df36..3a7aefe873 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Any kind of difference between two schemas is a SchemaDifference. @@ -480,17 +481,20 @@ public String getName() { class UnionModification implements SchemaModification, UnionDifference { private final String oldName; private final String newName; + private final boolean nameChanged; private final List details = new ArrayList<>(); public UnionModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.nameChanged = oldName.equals(newName); } public UnionModification(String newName) { - this.oldName = ""; + this.oldName = newName; this.newName = newName; + this.nameChanged = false; } public String getNewName() { @@ -509,6 +513,9 @@ public List getDetails(Class return (List) FpKit.filterList(details, clazz::isInstance); } + public boolean isNameChanged() { + return nameChanged; + } } interface UnionModificationDetail { @@ -626,6 +633,7 @@ public EnumModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; } + public EnumModification(String newName) { this.oldName = ""; this.newName = newName; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 8eed8af133..deef0a01db 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -58,7 +58,7 @@ class EditOperationAnalyzerTest extends Specification { (changes.unionChanges["U"] as UnionAddition).name == "U" } - def "union removed"() { + def "union deleted"() { given: def oldSdl = ''' type Query { @@ -85,6 +85,73 @@ class EditOperationAnalyzerTest extends Specification { (changes.unionChanges["U"] as UnionDeletion).name == "U" } + def "union renamed"() { + given: + def oldSdl = ''' + type Query { + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + def newSdl = ''' + type Query { + u: X + } + union X = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionModification + (changes.unionChanges["U"] as UnionModification).oldName == "U" + (changes.unionChanges["U"] as UnionModification).newName == "X" + } + + def "union renamed and member removed"() { + given: + def oldSdl = ''' + type Query { + u: U + } + union U = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + def newSdl = ''' + type Query { + u: X + } + union X = A + type A { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionModification + def unionDiff = changes.unionChanges["U"] as UnionModification + unionDiff.oldName == "U" + unionDiff.newName == "X" + unionDiff.getDetails(UnionMemberDeletion)[0].name == "B" + } + def "union member added"() { given: def oldSdl = ''' From 729cf2aa154fb0894e2ea1b792e95edd093f229f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 11:36:00 +1000 Subject: [PATCH 198/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 1 + .../ana/EditOperationAnalyzerTest.groovy | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 238456f532..d4f13d8573 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -691,6 +691,7 @@ private void changedUnion(EditOperation editOperation) { UnionModification objectModification = new UnionModification(oldUnionName, newUnionName); unionDifferences.put(oldUnionName, objectModification); + unionDifferences.put(newUnionName, objectModification); } // // private void changedUnion(EditOperation editOperation) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index deef0a01db..e8e622d95d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -114,6 +114,7 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: + changes.unionChanges["X"] === changes.unionChanges["U"] changes.unionChanges["U"] instanceof UnionModification (changes.unionChanges["U"] as UnionModification).oldName == "U" (changes.unionChanges["U"] as UnionModification).newName == "X" @@ -152,6 +153,40 @@ class EditOperationAnalyzerTest extends Specification { unionDiff.getDetails(UnionMemberDeletion)[0].name == "B" } + def "union renamed and member added"() { + given: + def oldSdl = ''' + type Query { + u: U + } + union U = A + type A { + foo: String + } + + ''' + def newSdl = ''' + type Query { + u: X + } + union X = A | B + type A { + foo: String + } + type B { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.unionChanges["U"] instanceof UnionModification + def unionDiff = changes.unionChanges["U"] as UnionModification + unionDiff.oldName == "U" + unionDiff.newName == "X" + unionDiff.getDetails(UnionMemberAddition)[0].name == "B" + } + def "union member added"() { given: def oldSdl = ''' From 7a06b9f504d49e9555d40c995d41558265469087 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 25 Oct 2022 11:41:26 +1000 Subject: [PATCH 199/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 8 +++--- .../schema/diffing/ana/SchemaDifference.java | 5 +++- .../ana/EditOperationAnalyzerTest.groovy | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index d4f13d8573..b709a4ead8 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -672,10 +672,12 @@ private void removedArgument(EditOperation editOperation) { } private void changedObject(EditOperation editOperation) { - String objectName = editOperation.getTargetVertex().getName(); + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); - ObjectModification objectModification = new ObjectModification(objectName); - objectDifferences.put(objectName, objectModification); + ObjectModification objectModification = new ObjectModification(oldName,newName); + objectDifferences.put(oldName, objectModification); + objectDifferences.put(newName, objectModification); } private void changedInterface(EditOperation editOperation) { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 3a7aefe873..d21b085d34 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -67,16 +67,19 @@ public String getName() { class ObjectModification implements SchemaModification, ObjectDifference { private final String oldName; private final String newName; + private final boolean renamed; private final List details = new ArrayList<>(); public ObjectModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.renamed = oldName.equals(newName); } public ObjectModification(String newName) { - this.oldName = ""; + this.oldName = newName; this.newName = newName; + this.renamed = false; } public List getDetails() { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index e8e622d95d..e436c16b6b 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -8,6 +8,31 @@ import static graphql.schema.diffing.ana.SchemaDifference.* class EditOperationAnalyzerTest extends Specification { + def "object renamed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + ''' + def newSdl = ''' + schema { + query: MyQuery + } + type MyQuery { + foo: String + } + + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] === changes.objectChanges["MyQuery"] + changes.objectChanges["Query"] instanceof ObjectModification + (changes.objectChanges["Query"] as ObjectModification).oldName == "Query" + (changes.objectChanges["Query"] as ObjectModification).newName == "MyQuery" + } + def "field renamed"() { given: def oldSdl = ''' From 23129dc49d8f0812eee80a8d794690604e847622 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Wed, 26 Oct 2022 10:09:07 +1000 Subject: [PATCH 200/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 10 +++--- .../schema/diffing/ana/SchemaDifference.java | 17 +++++++++- .../ana/EditOperationAnalyzerTest.groovy | 34 +++++++++++++++++-- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b709a4ead8..3dfbde5a3b 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -681,10 +681,12 @@ private void changedObject(EditOperation editOperation) { } private void changedInterface(EditOperation editOperation) { - String interfaceName = editOperation.getTargetVertex().getName(); - InterfaceModification interfaceModification = new InterfaceModification(interfaceName); - // we store the modification against the new name - interfaceDifferences.put(interfaceName, interfaceModification); + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); + + InterfaceModification interfaceModification = new InterfaceModification(oldName,newName); + interfaceDifferences.put(oldName, interfaceModification); + interfaceDifferences.put(newName, interfaceModification); } private void changedUnion(EditOperation editOperation) { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index d21b085d34..e3b8250b3e 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -287,22 +287,37 @@ public InterfaceDeletion(String name) { class InterfaceModification implements SchemaModification, InterfaceDifference { private final String oldName; private final String newName; + private final boolean renamed; private final List details = new ArrayList<>(); public InterfaceModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.renamed = oldName.equals(newName); } public InterfaceModification(String newName) { - this.oldName = ""; + this.oldName = newName; this.newName = newName; + this.renamed = false; } public List getDetails() { return details; } + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + + public boolean isRenamed() { + return renamed; + } + public List getDetails(Class clazz) { return (List) FpKit.filterList(details, clazz::isInstance); } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index e436c16b6b..a53fff7f3e 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -33,6 +33,34 @@ class EditOperationAnalyzerTest extends Specification { (changes.objectChanges["Query"] as ObjectModification).newName == "MyQuery" } + def "interface renamed"() { + given: + def oldSdl = ''' + type Query implements I { + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + type Query implements IRenamed { + foo: String + } + interface IRenamed { + foo: String + } + + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.interfaceChanges["I"] === changes.interfaceChanges["IRenamed"] + changes.interfaceChanges["I"] instanceof InterfaceModification + (changes.interfaceChanges["I"] as InterfaceModification).oldName == "I" + (changes.interfaceChanges["I"] as InterfaceModification).newName == "IRenamed" + } + def "field renamed"() { given: def oldSdl = ''' @@ -532,7 +560,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 1 + changes.interfaceChanges.size() == 2 + changes.interfaceChanges["Node"] === changes.interfaceChanges["Node2"] changes.interfaceChanges["Node2"] instanceof InterfaceModification } @@ -567,7 +596,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 2 + changes.interfaceChanges.size() == 3 + changes.interfaceChanges["Node"] == changes.interfaceChanges["Node2"] changes.interfaceChanges["Node2"] instanceof InterfaceModification changes.interfaceChanges["NewI"] instanceof InterfaceAddition changes.objectChanges.size() == 1 From f8d9ade1f45832c638bb4e36b283bc2f1e96cd55 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 27 Oct 2022 15:31:10 +1100 Subject: [PATCH 201/294] Update src/main/resources/i18n/Validation_de.properties Haben! Co-authored-by: Jordie <30464310+jord1e@users.noreply.github.com> --- src/main/resources/i18n/Validation_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index 728fe4d99d..22059223e4 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -60,7 +60,7 @@ ProvidedNonNullArguments.nullValue=Validierungsfehler ({0}) : Nullwert f\u00fcr ScalarLeaves.subselectionOnLeaf=Validierungsfehler ({0}) : Unterauswahl f\u00fcr Blatttyp ''{1}'' von Feld ''{2}'' nicht zul\u00e4ssig ScalarLeaves.subselectionRequired=Validierungsfehler ({0}) : Unterauswahl erforderlich f\u00fcr Typ ''{1}'' des Feldes ''{2}'' # -SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field +SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field haben SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field mit Fragmenten haben SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Subscription operation ''{1}'' root field ''{2}'' kann kein introspection field sein SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' kann kein introspection field sein From 6335cf0f5f40ca4327124fa0c756bcb91e57c4f3 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 27 Oct 2022 17:14:50 +1100 Subject: [PATCH 202/294] Float coercion tidy up (#2982) * Add all Float and Double coercion exception cases * Clean up tests, remove no longer allowed NaN value * Add early return for Doubles * Reorder NaN/Infinity check and add string representation to test * Remove unnecessary NaN check * Tidy package name * Tidy up --- .../execution/ValuesResolverLegacy.java | 10 ----- .../graphql/scalar/GraphqlFloatCoercing.java | 13 +++++- .../groovy/graphql/ScalarsFloatTest.groovy | 40 ++++++++++++++----- .../groovy/graphql/ScalarsQuerySchema.java | 14 ------- .../execution/ValuesResolverTestLegacy.groovy | 13 ++++-- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/main/java/graphql/execution/ValuesResolverLegacy.java b/src/main/java/graphql/execution/ValuesResolverLegacy.java index 5923f5248e..81bc80ccdc 100644 --- a/src/main/java/graphql/execution/ValuesResolverLegacy.java +++ b/src/main/java/graphql/execution/ValuesResolverLegacy.java @@ -77,9 +77,6 @@ static Value valueToLiteralLegacy(Object value, GraphQLType type, GraphQLCont // Since value is an internally represented value, it must be serialized // to an externally represented value before converting into an AST. final Object serialized = serializeLegacy(type, value, graphqlContext, locale); - if (isNullishLegacy(serialized)) { - return null; - } // Others serialize based on their corresponding JavaScript scalar types. if (serialized instanceof Boolean) { @@ -157,11 +154,4 @@ private static Object serializeLegacy(GraphQLType type, Object value, GraphQLCon return ((GraphQLEnumType) type).serialize(value, graphqlContext, locale); } } - - private static boolean isNullishLegacy(Object serialized) { - if (serialized instanceof Number) { - return Double.isNaN(((Number) serialized).doubleValue()); - } - return serialized == null; - } } diff --git a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java index 83261c4e2f..cb7d43b43b 100644 --- a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java @@ -29,18 +29,27 @@ public class GraphqlFloatCoercing implements Coercing { private Double convertImpl(Object input) { - if (isNumberIsh(input)) { + // From the GraphQL Float spec, non-finite floating-point internal values (NaN and Infinity) + // must raise a field error on both result and input coercion + Double doubleInput; + if (input instanceof Double) { + doubleInput = (Double) input; + } else if (isNumberIsh(input)) { BigDecimal value; try { value = new BigDecimal(input.toString()); } catch (NumberFormatException e) { return null; } - return value.doubleValue(); + doubleInput = value.doubleValue(); } else { return null; } + if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) { + return null; + } + return doubleInput; } @NotNull diff --git a/src/test/groovy/graphql/ScalarsFloatTest.groovy b/src/test/groovy/graphql/ScalarsFloatTest.groovy index 03de6335dc..07fea26fd1 100644 --- a/src/test/groovy/graphql/ScalarsFloatTest.groovy +++ b/src/test/groovy/graphql/ScalarsFloatTest.groovy @@ -114,24 +114,46 @@ class ScalarsFloatTest extends Specification { thrown(CoercingSerializeException) where: - value | _ - "" | _ - "not a number " | _ - Double.NaN | _ + value | _ + "" | _ + "not a number " | _ + Double.NaN | _ + Double.NaN.toString() | _ + Double.POSITIVE_INFINITY | _ + Double.POSITIVE_INFINITY.toString() | _ + Double.NEGATIVE_INFINITY | _ + Double.NEGATIVE_INFINITY.toString() | _ + Float.NaN | _ + Float.NaN.toString() | _ + Float.POSITIVE_INFINITY | _ + Float.POSITIVE_INFINITY.toString() | _ + Float.NEGATIVE_INFINITY | _ + Float.NEGATIVE_INFINITY.toString() | _ } @Unroll - def "serialize/parseValue throws exception for invalid input #value"() { + def "parseValue throws exception for invalid input #value"() { when: Scalars.GraphQLFloat.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) then: thrown(CoercingParseValueException) where: - value | _ - "" | _ - "not a number " | _ - Double.NaN | _ + value | _ + "" | _ + "not a number " | _ + Double.NaN | _ + Double.NaN.toString() | _ + Double.POSITIVE_INFINITY | _ + Double.POSITIVE_INFINITY.toString() | _ + Double.NEGATIVE_INFINITY | _ + Double.NEGATIVE_INFINITY.toString() | _ + Float.NaN | _ + Float.NaN.toString() | _ + Float.POSITIVE_INFINITY | _ + Float.POSITIVE_INFINITY.toString() | _ + Float.NEGATIVE_INFINITY | _ + Float.NEGATIVE_INFINITY.toString() | _ } } diff --git a/src/test/groovy/graphql/ScalarsQuerySchema.java b/src/test/groovy/graphql/ScalarsQuerySchema.java index 0967171422..44718be9e3 100644 --- a/src/test/groovy/graphql/ScalarsQuerySchema.java +++ b/src/test/groovy/graphql/ScalarsQuerySchema.java @@ -18,18 +18,6 @@ public class ScalarsQuerySchema { public static final GraphQLObjectType queryType = newObject() .name("QueryType") - // Static scalars - .field(newFieldDefinition() - .name("floatNaN") - .type(Scalars.GraphQLFloat) - .staticValue(Double.NaN)) // Retain for test coverage - // Scalars with input of same type, value echoed back - .field(newFieldDefinition() - .name("floatNaNInput") - .type(Scalars.GraphQLFloat) - .argument(newArgument() - .name("input") - .type(GraphQLNonNull.nonNull(Scalars.GraphQLFloat)))) .field(newFieldDefinition() .name("stringInput") .type(Scalars.GraphQLString) @@ -51,12 +39,10 @@ public class ScalarsQuerySchema { .type(Scalars.GraphQLString))) .build(); - static FieldCoordinates floatNaNCoordinates = FieldCoordinates.coordinates("QueryType", "floatNaNInput"); static FieldCoordinates stringInputCoordinates = FieldCoordinates.coordinates("QueryType", "stringInput"); static FieldCoordinates floatStringCoordinates = FieldCoordinates.coordinates("QueryType", "floatString"); static FieldCoordinates intStringCoordinates = FieldCoordinates.coordinates("QueryType", "intString"); static GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .dataFetcher(floatNaNCoordinates, inputDF) .dataFetcher(stringInputCoordinates, inputDF) .dataFetcher(floatStringCoordinates, inputDF) .dataFetcher(intStringCoordinates, inputDF) diff --git a/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy b/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy index ff883e0f78..0c944b7bb1 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTestLegacy.groovy @@ -1,6 +1,13 @@ -package graphql.language +package graphql.execution import graphql.GraphQLContext +import graphql.language.ArrayValue +import graphql.language.EnumValue +import graphql.language.FloatValue +import graphql.language.IntValue +import graphql.language.ObjectField +import graphql.language.ObjectValue +import graphql.language.StringValue import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLInputObjectType import spock.lang.Ignore @@ -137,7 +144,7 @@ class ValuesResolverTestLegacy extends Specification { def 'converts list to lists'() { expect: valueToLiteralLegacy(['hello', 'world'], list(GraphQLString), graphQLContext, locale).isEqualTo( - new ArrayValue(['hello', 'world']) + new ArrayValue([new StringValue('hello'), new StringValue('world')]) ) } @@ -145,7 +152,7 @@ class ValuesResolverTestLegacy extends Specification { String[] sArr = ['hello', 'world'] as String[] expect: valueToLiteralLegacy(sArr, list(GraphQLString), graphQLContext, locale).isEqualTo( - new ArrayValue(['hello', 'world']) + new ArrayValue([new StringValue('hello'), new StringValue('world')]) ) } From 866415aacfa81170ec7f65e0b363e24684f6ac5d Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 27 Oct 2022 16:29:29 +1000 Subject: [PATCH 203/294] work on higher level schema diff --- .../schema/diffing/SchemaGraphFactory.java | 4 +- .../diffing/ana/EditOperationAnalyzer.java | 62 +++++++++-- .../schema/diffing/ana/SchemaDifference.java | 31 +++++- .../ana/EditOperationAnalyzerTest.groovy | 104 +++++++++++++++++- 4 files changed, 185 insertions(+), 16 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index d07a20460b..253703c39e 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -232,9 +232,9 @@ private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgume GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(argumentVertex, typeVertex); - String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); + String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type) + ";defaultValue="; if (graphQLArgument.hasSetDefaultValue()) { - typeEdgeLabel += ";defaultValue=" + AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault())); + typeEdgeLabel += AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault())); } typeEdge.setLabel(typeEdgeLabel); schemaGraph.addEdge(typeEdge); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 3dfbde5a3b..58e663b28a 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -65,6 +65,7 @@ public EditOperationAnalyzer(GraphQLSchema oldSchema, public EditOperationAnalysisResult analyzeEdits(List editOperations, Mapping mapping) { handleTypeVertexChanges(editOperations); + for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { case CHANGE_VERTEX: @@ -317,15 +318,18 @@ private void changedTypeVertex(EditOperation editOperation) { case SchemaGraph.UNION: changedUnion(editOperation); break; -// case SchemaGraph.INPUT_OBJECT: -// changedInputObject(editOperation); -// break; -// case SchemaGraph.ENUM: -// changedEnum(editOperation); -// break; -// case SchemaGraph.SCALAR: -// changedScalar(editOperation); -// break; + case SchemaGraph.INPUT_OBJECT: + changedInputObject(editOperation); + break; + case SchemaGraph.ENUM: + changedEnum(editOperation); + break; + case SchemaGraph.SCALAR: + changedScalar(editOperation); + break; + case SchemaGraph.DIRECTIVE: + changedDirective(editOperation); + break; } } @@ -671,11 +675,47 @@ private void removedArgument(EditOperation editOperation) { } + private void changedEnum(EditOperation editOperation) { + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); + + EnumModification modification = new EnumModification(oldName, newName); + enumDifferences.put(oldName, modification); + enumDifferences.put(newName, modification); + } + + private void changedScalar(EditOperation editOperation) { + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); + + ScalarModification modification = new ScalarModification(oldName, newName); + scalarDifferences.put(oldName, modification); + scalarDifferences.put(newName, modification); + } + + private void changedInputObject(EditOperation editOperation) { + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); + + InputObjectModification modification = new InputObjectModification(oldName, newName); + inputObjectDifferences.put(oldName, modification); + inputObjectDifferences.put(newName, modification); + } + + private void changedDirective(EditOperation editOperation) { + String oldName = editOperation.getSourceVertex().getName(); + String newName = editOperation.getTargetVertex().getName(); + + DirectiveModification modification = new DirectiveModification(oldName, newName); + directiveDifferences.put(oldName, modification); + directiveDifferences.put(newName, modification); + } + private void changedObject(EditOperation editOperation) { String oldName = editOperation.getSourceVertex().getName(); String newName = editOperation.getTargetVertex().getName(); - ObjectModification objectModification = new ObjectModification(oldName,newName); + ObjectModification objectModification = new ObjectModification(oldName, newName); objectDifferences.put(oldName, objectModification); objectDifferences.put(newName, objectModification); } @@ -684,7 +724,7 @@ private void changedInterface(EditOperation editOperation) { String oldName = editOperation.getSourceVertex().getName(); String newName = editOperation.getTargetVertex().getName(); - InterfaceModification interfaceModification = new InterfaceModification(oldName,newName); + InterfaceModification interfaceModification = new InterfaceModification(oldName, newName); interfaceDifferences.put(oldName, interfaceModification); interfaceDifferences.put(newName, interfaceModification); } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index e3b8250b3e..4302829c1d 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -597,10 +597,17 @@ public String getName() { class InputObjectModification implements SchemaModification, InputObjectDifference { private final String oldName; private final String newName; + private final boolean nameChanged; + public InputObjectModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.nameChanged = oldName.equals(newName); + } + + public boolean isNameChanged() { + return nameChanged; } public String getNewName() { @@ -645,16 +652,23 @@ class EnumModification implements SchemaModification, EnumDifference { private final String oldName; private final String newName; + private final boolean nameChanged; private final List details = new ArrayList<>(); public EnumModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.nameChanged = oldName.equals(newName); } public EnumModification(String newName) { - this.oldName = ""; + this.oldName = newName; this.newName = newName; + this.nameChanged = false; + } + + public boolean isNameChanged() { + return nameChanged; } public String getNewName() { @@ -735,10 +749,17 @@ public String getName() { class ScalarModification implements SchemaModification, ScalarDifference { private final String oldName; private final String newName; + private final boolean nameChanged; + public ScalarModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.nameChanged = oldName.equals(newName); + } + + public boolean isNameChanged() { + return nameChanged; } public String getNewName() { @@ -779,13 +800,19 @@ public String getName() { } } - class DirectiveModification implements SchemaModification, SchemaDifference { + class DirectiveModification implements SchemaModification, DirectiveDifference { private final String oldName; private final String newName; + private final boolean nameChanged; public DirectiveModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; + this.nameChanged = oldName.equals(newName); + } + + public boolean isNameChanged() { + return nameChanged; } public String getNewName() { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a53fff7f3e..ec37e36fd1 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -2,6 +2,7 @@ package graphql.schema.diffing.ana import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing +import graphql.schema.idl.errors.DirectiveMissingNonNullArgumentError import spock.lang.Specification import static graphql.schema.diffing.ana.SchemaDifference.* @@ -609,6 +610,34 @@ class EditOperationAnalyzerTest extends Specification { } + def "enum renamed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + enum E { + A, B + } + ''' + def newSdl = ''' + type Query { + foo: ERenamed + } + enum ERenamed { + A, B + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.enumChanges["E"] === changes.enumChanges["ERenamed"] + def modification = changes.enumChanges["E"] as EnumModification + modification.oldName == "E" + modification.newName == "ERenamed" + + } + def "enum added"() { given: def oldSdl = ''' @@ -654,7 +683,6 @@ class EditOperationAnalyzerTest extends Specification { } - def "enum value added"() { given: def oldSdl = ''' @@ -747,6 +775,29 @@ class EditOperationAnalyzerTest extends Specification { (changes.scalarChanges["E"] as ScalarDeletion).getName() == "E" } + def "scalar renamed"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + scalar Foo + ''' + def newSdl = ''' + type Query { + foo: Bar + } + scalar Bar + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.scalarChanges["Foo"] === changes.scalarChanges["Bar"] + def modification = changes.scalarChanges["Foo"] as ScalarModification + modification.oldName == "Foo" + modification.newName == "Bar" + } + def "input object added"() { given: def oldSdl = ''' @@ -791,6 +842,35 @@ class EditOperationAnalyzerTest extends Specification { (changes.inputObjectChanges["I"] as InputObjectDeletion).getName() == "I" } + def "input object renamed"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: IRenamed): String + } + input IRenamed { + bar: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.inputObjectChanges["I"] === changes.inputObjectChanges["IRenamed"] + def modification = changes.inputObjectChanges["I"] as InputObjectModification + modification.oldName == "I" + modification.newName == "IRenamed" + } + + + def "directive added"() { given: def oldSdl = ''' @@ -831,6 +911,28 @@ class EditOperationAnalyzerTest extends Specification { (changes.directiveChanges["d"] as DirectiveDeletion).getName() == "d" } + def "directive renamed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @dRenamed on FIELD + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.directiveChanges["d"] === changes.directiveChanges["dRenamed"] + def modification = changes.directiveChanges["d"] as DirectiveModification + modification.oldName == "d" + modification.newName == "dRenamed" + } EditOperationAnalysisResult changes( From 630fc92f5190e2be72f246a2edf2baa8ff9a67d7 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 27 Oct 2022 16:52:27 +1000 Subject: [PATCH 204/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 33 ++++++ .../schema/diffing/ana/SchemaDifference.java | 104 ++++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 25 +++++ 3 files changed, 162 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 58e663b28a..030c36d110 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -88,6 +88,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleImplementsChanges(editOperations, mapping); handleUnionMemberChanges(editOperations, mapping); handleEnumValuesChanges(editOperations, mapping); + handleArgumentChanges(editOperations, mapping); return new EditOperationAnalysisResult( objectDifferences, @@ -155,6 +156,30 @@ private void handleEnumValuesChanges(List editOperations, Mapping } } + private void handleArgumentChanges(List editOperations, Mapping mapping) { + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case CHANGE_VERTEX: + if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { + handleArgumentChange(editOperation); + } + } + } + } + + private void handleArgumentChange(EditOperation editOperation) { + Vertex argument = editOperation.getTargetVertex(); + Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)) { + Vertex directive = fieldOrDirective; + DirectiveModification directiveModification = getDirectiveModification(directive.getName()); + String oldName = editOperation.getSourceVertex().getName(); + String newName = argument.getName(); + directiveModification.getDetails().add(new DirectiveArgumentRename(oldName, newName)); + + } + } + private void handleImplementsChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { Edge newEdge = editOperation.getTargetEdge(); @@ -549,6 +574,14 @@ private EnumModification getEnumModification(String newName) { return (EnumModification) enumDifferences.get(newName); } + private DirectiveModification getDirectiveModification(String newName) { + if (!directiveDifferences.containsKey(newName)) { + directiveDifferences.put(newName, new DirectiveModification(newName)); + } + assertTrue(directiveDifferences.get(newName) instanceof DirectiveModification); + return (DirectiveModification) directiveDifferences.get(newName); + } + private InterfaceModification getInterfaceModification(String newName) { if (!interfaceDifferences.containsKey(newName)) { interfaceDifferences.put(newName, new InterfaceModification(newName)); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 4302829c1d..9976e774b4 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -805,11 +805,18 @@ class DirectiveModification implements SchemaModification, DirectiveDifference { private final String newName; private final boolean nameChanged; + private final List details = new ArrayList<>(); + public DirectiveModification(String oldName, String newName) { this.oldName = oldName; this.newName = newName; this.nameChanged = oldName.equals(newName); } + public DirectiveModification( String newName) { + this.oldName = newName; + this.newName = newName; + this.nameChanged = false; + } public boolean isNameChanged() { return nameChanged; @@ -822,6 +829,103 @@ public String getNewName() { public String getOldName() { return oldName; } + + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + } + + interface DirectiveModificationDetail { + + } + + class DirectiveArgumentDeletion implements ObjectModificationDetail { + private final String name; + + public DirectiveArgumentDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + + class DirectiveArgumentAddition implements ObjectModificationDetail { + private final String name; + + public DirectiveArgumentAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class DirectiveArgumentTypeModification implements ObjectModificationDetail { + private final String oldType; + private final String newType; + + public DirectiveArgumentTypeModification(String oldType, String newType) { + this.oldType = oldType; + this.newType = newType; + } + + public String getNewType() { + return newType; + } + + public String getOldType() { + return oldType; + } + } + + class DirectiveArgumentDefaultValueModification implements ObjectModificationDetail { + private final String argumentName; + private final String oldValue; + private final String newValue; + + public DirectiveArgumentDefaultValueModification(String argumentName, String oldValue, String newValue) { + this.argumentName = argumentName; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public String getOldValue() { + return oldValue; + } + + public String getNewValue() { + return newValue; + } + + public String getArgumentName() { + return argumentName; + } + } + + class DirectiveArgumentRename implements DirectiveModificationDetail { + private final String oldName; + private final String newName; + + public DirectiveArgumentRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ec37e36fd1..5361343e94 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -934,6 +934,31 @@ class EditOperationAnalyzerTest extends Specification { modification.newName == "dRenamed" } + def "directive argument renamed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d(foo: String) on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d(bar:String) on FIELD + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.directiveChanges["d"] instanceof DirectiveModification + def renames = (changes.directiveChanges["d"] as DirectiveModification).getDetails(DirectiveArgumentRename) + renames[0].oldName == "foo" + renames[0].newName == "bar" + + + } + EditOperationAnalysisResult changes( String oldSdl, From 2b7edb59ce5cc487bb9af1e5d26b25fa854b1cf7 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 27 Oct 2022 17:08:56 +1000 Subject: [PATCH 205/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 19 ++++++++++ .../schema/diffing/ana/SchemaDifference.java | 35 +++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 38 ++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 030c36d110..f11cf583a0 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -177,6 +177,25 @@ private void handleArgumentChange(EditOperation editOperation) { String newName = argument.getName(); directiveModification.getDetails().add(new DirectiveArgumentRename(oldName, newName)); + }else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.FIELD)); + Vertex field = fieldOrDirective; + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); + if(fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + Vertex object = fieldsContainerForField; + ObjectModification objectModification = getObjectModification(object.getName()); + String oldName = editOperation.getSourceVertex().getName(); + String newName = argument.getName(); + objectModification.getDetails().add(new ObjectFieldArgumentRename(oldName, newName)); + }else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); + String oldName = editOperation.getSourceVertex().getName(); + String newName = argument.getName(); + interfaceModification.getDetails().add(new InterfaceFieldArgumentRename(oldName, newName)); + + } } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 9976e774b4..ae1279464c 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -161,6 +161,24 @@ public String getOldName() { } } + class ObjectFieldArgumentRename implements ObjectModificationDetail { + private final String oldName; + private final String newName; + + public ObjectFieldArgumentRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + class ObjectFieldTypeModification implements ObjectModificationDetail { private final String fieldName; private final String oldType; @@ -465,6 +483,23 @@ public String getArgumentName() { return argumentName; } } + class InterfaceFieldArgumentRename implements InterfaceModificationDetail { + private final String oldName; + private final String newName; + + public InterfaceFieldArgumentRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } // -----Union----------- diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5361343e94..52a69836df 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -336,7 +336,43 @@ class EditOperationAnalyzerTest extends Specification { typeModification[0].newType == "String!" } - def "argument removed"() { + def "object and interface field argument renamed"() { + given: + def oldSdl = ''' + type Query implements I{ + hello(arg: String): String + } + interface I { + hello(arg: String): String + } + ''' + def newSdl = ''' + type Query implements I{ + hello(argRename: String): String + } + interface I { + hello(argRename: String): String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModification + def objectModification = changes.objectChanges["Query"] as ObjectModification + def objectArgumentRenamed = objectModification.getDetails(ObjectFieldArgumentRename.class); + objectArgumentRenamed[0].oldName == "arg" + objectArgumentRenamed[0].newName == "argRename" + and: + changes.interfaceChanges["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceChanges["I"] as InterfaceModification + def interfaceArgumentRenamed = interfaceModification.getDetails(InterfaceFieldArgumentRename.class); + interfaceArgumentRenamed[0].oldName == "arg" + interfaceArgumentRenamed[0].newName == "argRename" + + } + + + def "object field argument removed"() { given: def oldSdl = ''' type Query { From cfe6d1124cdd7d6d5e6447ce7fbe0b51d3a3f52b Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 27 Oct 2022 18:36:53 +1000 Subject: [PATCH 206/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 22 +++++++++++ .../schema/diffing/ana/SchemaDifference.java | 12 +++++- .../ana/EditOperationAnalyzerTest.groovy | 38 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index f11cf583a0..bc53c814c8 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -415,6 +415,17 @@ private void typeEdgeInsertedForField(EditOperation editOperation, List newFields = objectModification.getDetails(ObjectFieldAddition.class); return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); } + private boolean isFieldNewForExistingInterface(String interfaceName, String fieldName) { + if (!interfaceDifferences.containsKey(interfaceName)) { + return false; + } + if (!(interfaceDifferences.get(interfaceName) instanceof InterfaceModification)) { + return false; + } + InterfaceModification interfaceModification = (InterfaceModification) interfaceDifferences.get(interfaceName); + List newFields = interfaceModification.getDetails(InterfaceFieldAddition.class); + return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } private boolean isObjectDeleted(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectDeletion; diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index ae1279464c..a04a55ed62 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -190,6 +190,10 @@ public ObjectFieldTypeModification(String fieldName, String oldType, String newT this.newType = newType; } + public String getFieldName() { + return fieldName; + } + public String getNewType() { return newType; } @@ -394,14 +398,20 @@ public String getName() { } class InterfaceFieldTypeModification implements InterfaceModificationDetail { + private final String fieldName; private final String oldType; private final String newType; - public InterfaceFieldTypeModification(String oldType, String newType) { + public InterfaceFieldTypeModification(String fieldName, String oldType, String newType) { + this.fieldName = fieldName; this.oldType = oldType; this.newType = newType; } + public String getFieldName() { + return fieldName; + } + public String getNewType() { return newType; } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 52a69836df..450b0e4f60 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -507,6 +507,44 @@ class EditOperationAnalyzerTest extends Specification { } + def "Interface and Object field type changed"() { + given: + def oldSdl = ''' + type Query implements I{ + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + type Query implements I{ + foo: ID + } + interface I { + foo: ID + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.interfaceChanges["I"] instanceof InterfaceModification + def iModification = changes.interfaceChanges["I"] as InterfaceModification + def iFieldTypeModifications = iModification.getDetails(InterfaceFieldTypeModification) + iFieldTypeModifications[0].fieldName == "foo" + iFieldTypeModifications[0].oldType == "String" + iFieldTypeModifications[0].newType == "ID" + and: + changes.objectChanges["Query"] instanceof ObjectModification + def oModification = changes.objectChanges["Query"] as ObjectModification + def oFieldTypeModifications = oModification.getDetails(ObjectFieldTypeModification) + oFieldTypeModifications[0].fieldName == "foo" + oFieldTypeModifications[0].oldType == "String" + oFieldTypeModifications[0].newType == "ID" + + + } + def "new Interface introduced"() { given: def oldSdl = ''' From a5ce15161bb5c48818425f363133ca2c5747be51 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Thu, 27 Oct 2022 18:41:29 +1000 Subject: [PATCH 207/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 10 +++++ .../ana/EditOperationAnalyzerTest.groovy | 38 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index bc53c814c8..519394b883 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -495,8 +495,18 @@ private void fieldTypeChanged(EditOperation editOperation) { String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); objectModification.getDetails().add(new ObjectFieldTypeModification(fieldName, oldType, newType)); + }else { + assertTrue(container.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = container; + InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); + String fieldName = field.getName(); + String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + interfaceModification.getDetails().add(new InterfaceFieldTypeModification(fieldName, oldType, newType)); + } + } // TODO: this is not great, we should avoid parsing the label like that diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 450b0e4f60..261dbddafe 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -507,7 +507,7 @@ class EditOperationAnalyzerTest extends Specification { } - def "Interface and Object field type changed"() { + def "Interface and Object field type changed completely"() { given: def oldSdl = ''' type Query implements I{ @@ -541,6 +541,42 @@ class EditOperationAnalyzerTest extends Specification { oFieldTypeModifications[0].fieldName == "foo" oFieldTypeModifications[0].oldType == "String" oFieldTypeModifications[0].newType == "ID" + } + + def "Interface and Object field type changed wrapping type"() { + given: + def oldSdl = ''' + type Query implements I{ + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + type Query implements I{ + foo: [String!] + } + interface I { + foo: [String!] + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.interfaceChanges["I"] instanceof InterfaceModification + def iModification = changes.interfaceChanges["I"] as InterfaceModification + def iFieldTypeModifications = iModification.getDetails(InterfaceFieldTypeModification) + iFieldTypeModifications[0].fieldName == "foo" + iFieldTypeModifications[0].oldType == "String" + iFieldTypeModifications[0].newType == "[String!]" + and: + changes.objectChanges["Query"] instanceof ObjectModification + def oModification = changes.objectChanges["Query"] as ObjectModification + def oFieldTypeModifications = oModification.getDetails(ObjectFieldTypeModification) + oFieldTypeModifications[0].fieldName == "foo" + oFieldTypeModifications[0].oldType == "String" + oFieldTypeModifications[0].newType == "[String!]" } From 9c359c516c3bf0e8b3404b211579ceca8921c5c7 Mon Sep 17 00:00:00 2001 From: George Leontiev Date: Thu, 27 Oct 2022 13:04:03 +0100 Subject: [PATCH 208/294] Only run GCP tests on main graphql-java repo (skip on forks). --- .github/workflows/invoke_test_runner.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/invoke_test_runner.yml b/.github/workflows/invoke_test_runner.yml index 0792700115..0ebead00d6 100644 --- a/.github/workflows/invoke_test_runner.yml +++ b/.github/workflows/invoke_test_runner.yml @@ -31,6 +31,7 @@ env: jobs: execute_workflow: + if: github.repository == 'graphql-java/graphql-java' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From b9210a02951fcd093ee7929148e917b1c0878cf9 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 03:54:14 +1000 Subject: [PATCH 209/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 50 +++++++++++++--- .../ana/EditOperationAnalyzerTest.groovy | 58 +++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 519394b883..b2e1d1988b 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -177,17 +177,17 @@ private void handleArgumentChange(EditOperation editOperation) { String newName = argument.getName(); directiveModification.getDetails().add(new DirectiveArgumentRename(oldName, newName)); - }else { + } else { assertTrue(fieldOrDirective.isOfType(SchemaGraph.FIELD)); Vertex field = fieldOrDirective; Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); - if(fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { Vertex object = fieldsContainerForField; ObjectModification objectModification = getObjectModification(object.getName()); String oldName = editOperation.getSourceVertex().getName(); String newName = argument.getName(); objectModification.getDetails().add(new ObjectFieldArgumentRename(oldName, newName)); - }else { + } else { assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = fieldsContainerForField; InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); @@ -201,13 +201,19 @@ private void handleArgumentChange(EditOperation editOperation) { private void handleImplementsChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { - Edge newEdge = editOperation.getTargetEdge(); switch (editOperation.getOperation()) { case INSERT_EDGE: + Edge newEdge = editOperation.getTargetEdge(); if (newEdge.getLabel().startsWith("implements ")) { newInterfaceAddedToInterfaceOrObject(newEdge); } break; + case DELETE_EDGE: + Edge oldEdge = editOperation.getSourceEdge(); + if (oldEdge.getLabel().startsWith("implements ")) { + interfaceImplementationDeleted(oldEdge); + } + break; } } } @@ -495,7 +501,7 @@ private void fieldTypeChanged(EditOperation editOperation) { String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); objectModification.getDetails().add(new ObjectFieldTypeModification(fieldName, oldType, newType)); - }else { + } else { assertTrue(container.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = container; InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); @@ -525,6 +531,30 @@ private String getDefaultValueFromEdgeLabel(Edge edge) { } + private void interfaceImplementationDeleted(Edge deletedEdge) { + Vertex from = deletedEdge.getFrom(); + if (from.isOfType(SchemaGraph.OBJECT)) { + if (isObjectDeleted(from.getName())) { + return; + } + Vertex objectVertex = deletedEdge.getFrom(); + Vertex interfaceVertex = deletedEdge.getTo(); + ObjectInterfaceImplementationDeletion deletion = new ObjectInterfaceImplementationDeletion(interfaceVertex.getName()); + getObjectModification(objectVertex.getName()).getDetails().add(deletion); + + } else { + assertTrue(from.isOfType(SchemaGraph.INTERFACE)); + if (isInterfaceDeleted(from.getName())) { + return; + } + Vertex interfaceFromVertex = deletedEdge.getFrom(); + Vertex interfaceVertex = deletedEdge.getTo(); + InterfaceInterfaceImplementationDeletion deletion = new InterfaceInterfaceImplementationDeletion(interfaceVertex.getName()); + getInterfaceModification(interfaceFromVertex.getName()).getDetails().add(deletion); + } + + } + private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { Vertex from = newEdge.getFrom(); if (from.isOfType(SchemaGraph.OBJECT)) { @@ -536,7 +566,8 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { ObjectInterfaceImplementationAddition objectInterfaceImplementationAddition = new ObjectInterfaceImplementationAddition(interfaceVertex.getName()); getObjectModification(objectVertex.getName()).getDetails().add(objectInterfaceImplementationAddition); - } else if (from.isOfType(SchemaGraph.INTERFACE)) { + } else { + assertTrue(from.isOfType(SchemaGraph.INTERFACE)); if (isInterfaceAdded(from.getName())) { return; } @@ -544,8 +575,6 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { Vertex interfaceVertex = newEdge.getTo(); InterfaceInterfaceImplementationAddition addition = new InterfaceInterfaceImplementationAddition(interfaceVertex.getName()); getInterfaceModification(interfaceFromVertex.getName()).getDetails().add(addition); - } else { - Assert.assertShouldNeverHappen("expected an implementation edge"); } } @@ -581,6 +610,7 @@ private boolean isFieldNewForExistingObject(String objectName, String fieldName) List newFields = objectModification.getDetails(ObjectFieldAddition.class); return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); } + private boolean isFieldNewForExistingInterface(String interfaceName, String fieldName) { if (!interfaceDifferences.containsKey(interfaceName)) { return false; @@ -597,6 +627,10 @@ private boolean isObjectDeleted(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectDeletion; } + private boolean isInterfaceDeleted(String name) { + return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceDeletion; + } + private boolean isInterfaceAdded(String name) { return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceAddition; } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 261dbddafe..7355498d73 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -62,6 +62,64 @@ class EditOperationAnalyzerTest extends Specification { (changes.interfaceChanges["I"] as InterfaceModification).newName == "IRenamed" } + def "interface removed from object"() { + given: + def oldSdl = ''' + type Query implements I { + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + type Query{ + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.objectChanges["Query"] instanceof ObjectModification + def implementationDeletions = (changes.objectChanges["Query"] as ObjectModification).getDetails(ObjectInterfaceImplementationDeletion) + implementationDeletions[0].name == "I" + } + + def "interface removed from interface"() { + given: + def oldSdl = ''' + type Query { + foo: Foo + } + interface FooI { + foo: String + } + interface Foo implements FooI { + foo: String + } + type FooImpl implements Foo & FooI { + foo: String + } + ''' + def newSdl = ''' + type Query { + foo: Foo + } + interface Foo { + foo: String + } + type FooImpl implements Foo { + foo: String + } + ''' + when: + def changes = changes(oldSdl, newSdl) + then: + changes.interfaceChanges["Foo"] instanceof InterfaceModification + def implementationDeletions = (changes.interfaceChanges["Foo"] as InterfaceModification).getDetails(InterfaceInterfaceImplementationDeletion) + implementationDeletions[0].name == "FooI" + } + def "field renamed"() { given: def oldSdl = ''' From 5d20db24c903a96170290d807b81b03cbedc9c94 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 03:56:11 +1000 Subject: [PATCH 210/294] work on higher level schema diff --- .../ana/EditOperationAnalysisResult.java | 68 +++--- .../ana/EditOperationAnalyzerTest.groovy | 217 +++++++++--------- 2 files changed, 142 insertions(+), 143 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java index e1b9c754b0..1c37d0755e 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -6,56 +6,56 @@ @ExperimentalApi public class EditOperationAnalysisResult { - private final Map objectChanges; - private final Map interfaceChanges; - private final Map unionChanges; - private final Map enumChanges; - private final Map inputObjectChanges; - private final Map scalarChanges; + private final Map objectDifferences; + private final Map interfaceDifferences; + private final Map unionDifferences; + private final Map enumDifferences; + private final Map inputObjectDifferences; + private final Map scalarDifferences; - private final Map directiveChanges; + private final Map directiveDifferences; public EditOperationAnalysisResult(Map objectChanges, - Map interfaceChanges, - Map unionChanges, - Map enumChanges, - Map inputObjectChanges, - Map scalarChanges, - Map directiveChanges) { - this.objectChanges = objectChanges; - this.interfaceChanges = interfaceChanges; - this.unionChanges = unionChanges; - this.enumChanges = enumChanges; - this.inputObjectChanges = inputObjectChanges; - this.scalarChanges = scalarChanges; - this.directiveChanges = directiveChanges; + Map interfaceDifferences, + Map unionDifferences, + Map enumDifferences, + Map inputObjectDifferences, + Map scalarDifferences, + Map directiveDifferences) { + this.objectDifferences = objectChanges; + this.interfaceDifferences = interfaceDifferences; + this.unionDifferences = unionDifferences; + this.enumDifferences = enumDifferences; + this.inputObjectDifferences = inputObjectDifferences; + this.scalarDifferences = scalarDifferences; + this.directiveDifferences = directiveDifferences; } - public Map getObjectChanges() { - return objectChanges; + public Map getObjectDifferences() { + return objectDifferences; } - public Map getInterfaceChanges() { - return interfaceChanges; + public Map getInterfaceDifferences() { + return interfaceDifferences; } - public Map getUnionChanges() { - return unionChanges; + public Map getUnionDifferences() { + return unionDifferences; } - public Map getEnumChanges() { - return enumChanges; + public Map getEnumDifferences() { + return enumDifferences; } - public Map getInputObjectChanges() { - return inputObjectChanges; + public Map getInputObjectDifferences() { + return inputObjectDifferences; } - public Map getScalarChanges() { - return scalarChanges; + public Map getScalarDifferences() { + return scalarDifferences; } - public Map getDirectiveChanges() { - return directiveChanges; + public Map getDirectiveDifferences() { + return directiveDifferences; } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 7355498d73..cf50bab4db 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -2,7 +2,6 @@ package graphql.schema.diffing.ana import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing -import graphql.schema.idl.errors.DirectiveMissingNonNullArgumentError import spock.lang.Specification import static graphql.schema.diffing.ana.SchemaDifference.* @@ -28,10 +27,10 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] === changes.objectChanges["MyQuery"] - changes.objectChanges["Query"] instanceof ObjectModification - (changes.objectChanges["Query"] as ObjectModification).oldName == "Query" - (changes.objectChanges["Query"] as ObjectModification).newName == "MyQuery" + changes.objectDifferences["Query"] === changes.objectDifferences["MyQuery"] + changes.objectDifferences["Query"] instanceof ObjectModification + (changes.objectDifferences["Query"] as ObjectModification).oldName == "Query" + (changes.objectDifferences["Query"] as ObjectModification).newName == "MyQuery" } def "interface renamed"() { @@ -56,10 +55,10 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges["I"] === changes.interfaceChanges["IRenamed"] - changes.interfaceChanges["I"] instanceof InterfaceModification - (changes.interfaceChanges["I"] as InterfaceModification).oldName == "I" - (changes.interfaceChanges["I"] as InterfaceModification).newName == "IRenamed" + changes.interfaceDifferences["I"] === changes.interfaceDifferences["IRenamed"] + changes.interfaceDifferences["I"] instanceof InterfaceModification + (changes.interfaceDifferences["I"] as InterfaceModification).oldName == "I" + (changes.interfaceDifferences["I"] as InterfaceModification).newName == "IRenamed" } def "interface removed from object"() { @@ -80,8 +79,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def implementationDeletions = (changes.objectChanges["Query"] as ObjectModification).getDetails(ObjectInterfaceImplementationDeletion) + changes.objectDifferences["Query"] instanceof ObjectModification + def implementationDeletions = (changes.objectDifferences["Query"] as ObjectModification).getDetails(ObjectInterfaceImplementationDeletion) implementationDeletions[0].name == "I" } @@ -115,8 +114,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges["Foo"] instanceof InterfaceModification - def implementationDeletions = (changes.interfaceChanges["Foo"] as InterfaceModification).getDetails(InterfaceInterfaceImplementationDeletion) + changes.interfaceDifferences["Foo"] instanceof InterfaceModification + def implementationDeletions = (changes.interfaceDifferences["Foo"] as InterfaceModification).getDetails(InterfaceInterfaceImplementationDeletion) implementationDeletions[0].name == "FooI" } @@ -135,8 +134,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def fieldRenames = objectModification.getDetails(ObjectFieldRename.class) fieldRenames[0].oldName == "hello" fieldRenames[0].newName == "hello2" @@ -166,8 +165,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionAddition - (changes.unionChanges["U"] as UnionAddition).name == "U" + changes.unionDifferences["U"] instanceof UnionAddition + (changes.unionDifferences["U"] as UnionAddition).name == "U" } def "union deleted"() { @@ -193,8 +192,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionDeletion - (changes.unionChanges["U"] as UnionDeletion).name == "U" + changes.unionDifferences["U"] instanceof UnionDeletion + (changes.unionDifferences["U"] as UnionDeletion).name == "U" } def "union renamed"() { @@ -226,10 +225,10 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["X"] === changes.unionChanges["U"] - changes.unionChanges["U"] instanceof UnionModification - (changes.unionChanges["U"] as UnionModification).oldName == "U" - (changes.unionChanges["U"] as UnionModification).newName == "X" + changes.unionDifferences["X"] === changes.unionDifferences["U"] + changes.unionDifferences["U"] instanceof UnionModification + (changes.unionDifferences["U"] as UnionModification).oldName == "U" + (changes.unionDifferences["U"] as UnionModification).newName == "X" } def "union renamed and member removed"() { @@ -258,8 +257,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionModification - def unionDiff = changes.unionChanges["U"] as UnionModification + changes.unionDifferences["U"] instanceof UnionModification + def unionDiff = changes.unionDifferences["U"] as UnionModification unionDiff.oldName == "U" unionDiff.newName == "X" unionDiff.getDetails(UnionMemberDeletion)[0].name == "B" @@ -292,8 +291,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionModification - def unionDiff = changes.unionChanges["U"] as UnionModification + changes.unionDifferences["U"] instanceof UnionModification + def unionDiff = changes.unionDifferences["U"] as UnionModification unionDiff.oldName == "U" unionDiff.newName == "X" unionDiff.getDetails(UnionMemberAddition)[0].name == "B" @@ -334,8 +333,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionModification - def unionModification = changes.unionChanges["U"] as UnionModification + changes.unionDifferences["U"] instanceof UnionModification + def unionModification = changes.unionDifferences["U"] as UnionModification unionModification.getDetails(UnionMemberAddition)[0].name == "C" } @@ -367,8 +366,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.unionChanges["U"] instanceof UnionModification - def unionModification = changes.unionChanges["U"] as UnionModification + changes.unionDifferences["U"] instanceof UnionModification + def unionModification = changes.unionDifferences["U"] as UnionModification unionModification.getDetails(UnionMemberDeletion)[0].name == "B" } @@ -387,8 +386,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def typeModification = objectModification.getDetails(ObjectFieldTypeModification.class) typeModification[0].oldType == "String" typeModification[0].newType == "String!" @@ -415,14 +414,14 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def objectArgumentRenamed = objectModification.getDetails(ObjectFieldArgumentRename.class); objectArgumentRenamed[0].oldName == "arg" objectArgumentRenamed[0].newName == "argRename" and: - changes.interfaceChanges["I"] instanceof InterfaceModification - def interfaceModification = changes.interfaceChanges["I"] as InterfaceModification + changes.interfaceDifferences["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["I"] as InterfaceModification def interfaceArgumentRenamed = interfaceModification.getDetails(InterfaceFieldArgumentRename.class); interfaceArgumentRenamed[0].oldName == "arg" interfaceArgumentRenamed[0].newName == "argRename" @@ -445,8 +444,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def argumentRemoved = objectModification.getDetails(ObjectFieldArgumentDeletion.class); argumentRemoved[0].fieldName == "hello" argumentRemoved[0].name == "arg" @@ -475,16 +474,16 @@ class EditOperationAnalyzerTest extends Specification { def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def objDefaultValueModified = objectModification.getDetails(ObjectFieldArgumentDefaultValueModification.class); objDefaultValueModified[0].fieldName == "foo" objDefaultValueModified[0].argumentName == "arg" objDefaultValueModified[0].oldValue == '"bar"' objDefaultValueModified[0].newValue == '"barChanged"' and: - changes.interfaceChanges["Foo"] instanceof InterfaceModification - def interfaceModification = changes.interfaceChanges["Foo"] as InterfaceModification + changes.interfaceDifferences["Foo"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["Foo"] as InterfaceModification def intDefaultValueModified = interfaceModification.getDetails(InterfaceFieldArgumentDefaultValueModification.class); intDefaultValueModified[0].fieldName == "foo" intDefaultValueModified[0].argumentName == "arg" @@ -508,8 +507,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Query"] instanceof ObjectModification - def objectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification def fieldAdded = objectModification.getDetails(ObjectFieldAddition) fieldAdded[0].name == "newOne" } @@ -533,7 +532,7 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Foo"] instanceof ObjectAddition + changes.objectDifferences["Foo"] instanceof ObjectAddition } def "object removed and field type changed"() { @@ -554,10 +553,10 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.objectChanges["Foo"] instanceof ObjectDeletion - (changes.objectChanges["Foo"] as ObjectDeletion).name == "Foo" - changes.objectChanges["Query"] instanceof ObjectModification - def queryObjectModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Foo"] instanceof ObjectDeletion + (changes.objectDifferences["Foo"] as ObjectDeletion).name == "Foo" + changes.objectDifferences["Query"] instanceof ObjectModification + def queryObjectModification = changes.objectDifferences["Query"] as ObjectModification queryObjectModification.details.size() == 1 queryObjectModification.details[0] instanceof ObjectFieldTypeModification (queryObjectModification.details[0] as ObjectFieldTypeModification).oldType == "Foo" @@ -586,15 +585,15 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges["I"] instanceof InterfaceModification - def iModification = changes.interfaceChanges["I"] as InterfaceModification + changes.interfaceDifferences["I"] instanceof InterfaceModification + def iModification = changes.interfaceDifferences["I"] as InterfaceModification def iFieldTypeModifications = iModification.getDetails(InterfaceFieldTypeModification) iFieldTypeModifications[0].fieldName == "foo" iFieldTypeModifications[0].oldType == "String" iFieldTypeModifications[0].newType == "ID" and: - changes.objectChanges["Query"] instanceof ObjectModification - def oModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def oModification = changes.objectDifferences["Query"] as ObjectModification def oFieldTypeModifications = oModification.getDetails(ObjectFieldTypeModification) oFieldTypeModifications[0].fieldName == "foo" oFieldTypeModifications[0].oldType == "String" @@ -622,15 +621,15 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges["I"] instanceof InterfaceModification - def iModification = changes.interfaceChanges["I"] as InterfaceModification + changes.interfaceDifferences["I"] instanceof InterfaceModification + def iModification = changes.interfaceDifferences["I"] as InterfaceModification def iFieldTypeModifications = iModification.getDetails(InterfaceFieldTypeModification) iFieldTypeModifications[0].fieldName == "foo" iFieldTypeModifications[0].oldType == "String" iFieldTypeModifications[0].newType == "[String!]" and: - changes.objectChanges["Query"] instanceof ObjectModification - def oModification = changes.objectChanges["Query"] as ObjectModification + changes.objectDifferences["Query"] instanceof ObjectModification + def oModification = changes.objectDifferences["Query"] as ObjectModification def oFieldTypeModifications = oModification.getDetails(ObjectFieldTypeModification) oFieldTypeModifications[0].fieldName == "foo" oFieldTypeModifications[0].oldType == "String" @@ -663,11 +662,11 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 1 - changes.interfaceChanges["Node"] instanceof InterfaceAddition - changes.objectChanges.size() == 1 - changes.objectChanges["Foo"] instanceof ObjectModification - def objectModification = changes.objectChanges["Foo"] as ObjectModification + changes.interfaceDifferences.size() == 1 + changes.interfaceDifferences["Node"] instanceof InterfaceAddition + changes.objectDifferences.size() == 1 + changes.objectDifferences["Foo"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Foo"] as ObjectModification def addedInterfaceDetails = objectModification.getDetails(ObjectInterfaceImplementationAddition.class) addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "Node" @@ -694,12 +693,12 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 1 - changes.interfaceChanges["Node"] instanceof InterfaceAddition - changes.objectChanges.size() == 2 - changes.objectChanges["Foo"] instanceof ObjectAddition - changes.objectChanges["Query"] instanceof ObjectModification - (changes.objectChanges["Query"] as ObjectModification).getDetails()[0] instanceof ObjectFieldTypeModification + changes.interfaceDifferences.size() == 1 + changes.interfaceDifferences["Node"] instanceof InterfaceAddition + changes.objectDifferences.size() == 2 + changes.objectDifferences["Foo"] instanceof ObjectAddition + changes.objectDifferences["Query"] instanceof ObjectModification + (changes.objectDifferences["Query"] as ObjectModification).getDetails()[0] instanceof ObjectFieldTypeModification } def "interfaced renamed"() { @@ -729,9 +728,9 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 2 - changes.interfaceChanges["Node"] === changes.interfaceChanges["Node2"] - changes.interfaceChanges["Node2"] instanceof InterfaceModification + changes.interfaceDifferences.size() == 2 + changes.interfaceDifferences["Node"] === changes.interfaceDifferences["Node2"] + changes.interfaceDifferences["Node2"] instanceof InterfaceModification } def "interfaced renamed and another interface added to it"() { @@ -765,13 +764,13 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.interfaceChanges.size() == 3 - changes.interfaceChanges["Node"] == changes.interfaceChanges["Node2"] - changes.interfaceChanges["Node2"] instanceof InterfaceModification - changes.interfaceChanges["NewI"] instanceof InterfaceAddition - changes.objectChanges.size() == 1 - changes.objectChanges["Foo"] instanceof ObjectModification - def objectModification = changes.objectChanges["Foo"] as ObjectModification + changes.interfaceDifferences.size() == 3 + changes.interfaceDifferences["Node"] == changes.interfaceDifferences["Node2"] + changes.interfaceDifferences["Node2"] instanceof InterfaceModification + changes.interfaceDifferences["NewI"] instanceof InterfaceAddition + changes.objectDifferences.size() == 1 + changes.objectDifferences["Foo"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Foo"] as ObjectModification def addedInterfaceDetails = objectModification.getDetails(ObjectInterfaceImplementationAddition) addedInterfaceDetails.size() == 1 addedInterfaceDetails[0].name == "NewI" @@ -799,8 +798,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.enumChanges["E"] === changes.enumChanges["ERenamed"] - def modification = changes.enumChanges["E"] as EnumModification + changes.enumDifferences["E"] === changes.enumDifferences["ERenamed"] + def modification = changes.enumDifferences["E"] as EnumModification modification.oldName == "E" modification.newName == "ERenamed" @@ -824,8 +823,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.enumChanges["E"] instanceof EnumAddition - (changes.enumChanges["E"] as EnumAddition).getName() == "E" + changes.enumDifferences["E"] instanceof EnumAddition + (changes.enumDifferences["E"] as EnumAddition).getName() == "E" } def "enum deleted"() { @@ -846,8 +845,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.enumChanges["E"] instanceof EnumDeletion - (changes.enumChanges["E"] as EnumDeletion).getName() == "E" + changes.enumDifferences["E"] instanceof EnumDeletion + (changes.enumDifferences["E"] as EnumDeletion).getName() == "E" } @@ -872,8 +871,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.enumChanges["E"] instanceof EnumModification - def enumModification = changes.enumChanges["E"] as EnumModification + changes.enumDifferences["E"] instanceof EnumModification + def enumModification = changes.enumDifferences["E"] as EnumModification enumModification.getDetails(EnumValueAddition)[0].name == "B" } @@ -898,8 +897,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.enumChanges["E"] instanceof EnumModification - def enumModification = changes.enumChanges["E"] as EnumModification + changes.enumDifferences["E"] instanceof EnumModification + def enumModification = changes.enumDifferences["E"] as EnumModification enumModification.getDetails(EnumValueDeletion)[0].name == "B" } @@ -919,8 +918,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.scalarChanges["E"] instanceof ScalarAddition - (changes.scalarChanges["E"] as ScalarAddition).getName() == "E" + changes.scalarDifferences["E"] instanceof ScalarAddition + (changes.scalarDifferences["E"] as ScalarAddition).getName() == "E" } def "scalar deleted"() { @@ -939,8 +938,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.scalarChanges["E"] instanceof ScalarDeletion - (changes.scalarChanges["E"] as ScalarDeletion).getName() == "E" + changes.scalarDifferences["E"] instanceof ScalarDeletion + (changes.scalarDifferences["E"] as ScalarDeletion).getName() == "E" } def "scalar renamed"() { @@ -960,8 +959,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.scalarChanges["Foo"] === changes.scalarChanges["Bar"] - def modification = changes.scalarChanges["Foo"] as ScalarModification + changes.scalarDifferences["Foo"] === changes.scalarDifferences["Bar"] + def modification = changes.scalarDifferences["Foo"] as ScalarModification modification.oldName == "Foo" modification.newName == "Bar" } @@ -984,8 +983,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.inputObjectChanges["I"] instanceof InputObjectAddition - (changes.inputObjectChanges["I"] as InputObjectAddition).getName() == "I" + changes.inputObjectDifferences["I"] instanceof InputObjectAddition + (changes.inputObjectDifferences["I"] as InputObjectAddition).getName() == "I" } def "input object deleted"() { @@ -1006,8 +1005,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.inputObjectChanges["I"] instanceof InputObjectDeletion - (changes.inputObjectChanges["I"] as InputObjectDeletion).getName() == "I" + changes.inputObjectDifferences["I"] instanceof InputObjectDeletion + (changes.inputObjectDifferences["I"] as InputObjectDeletion).getName() == "I" } def "input object renamed"() { @@ -1031,8 +1030,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.inputObjectChanges["I"] === changes.inputObjectChanges["IRenamed"] - def modification = changes.inputObjectChanges["I"] as InputObjectModification + changes.inputObjectDifferences["I"] === changes.inputObjectDifferences["IRenamed"] + def modification = changes.inputObjectDifferences["I"] as InputObjectModification modification.oldName == "I" modification.newName == "IRenamed" } @@ -1055,8 +1054,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.directiveChanges["d"] instanceof DirectiveAddition - (changes.directiveChanges["d"] as DirectiveAddition).getName() == "d" + changes.directiveDifferences["d"] instanceof DirectiveAddition + (changes.directiveDifferences["d"] as DirectiveAddition).getName() == "d" } def "directive deleted"() { @@ -1075,8 +1074,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.directiveChanges["d"] instanceof DirectiveDeletion - (changes.directiveChanges["d"] as DirectiveDeletion).getName() == "d" + changes.directiveDifferences["d"] instanceof DirectiveDeletion + (changes.directiveDifferences["d"] as DirectiveDeletion).getName() == "d" } def "directive renamed"() { @@ -1096,8 +1095,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.directiveChanges["d"] === changes.directiveChanges["dRenamed"] - def modification = changes.directiveChanges["d"] as DirectiveModification + changes.directiveDifferences["d"] === changes.directiveDifferences["dRenamed"] + def modification = changes.directiveDifferences["d"] as DirectiveModification modification.oldName == "d" modification.newName == "dRenamed" } @@ -1119,8 +1118,8 @@ class EditOperationAnalyzerTest extends Specification { when: def changes = changes(oldSdl, newSdl) then: - changes.directiveChanges["d"] instanceof DirectiveModification - def renames = (changes.directiveChanges["d"] as DirectiveModification).getDetails(DirectiveArgumentRename) + changes.directiveDifferences["d"] instanceof DirectiveModification + def renames = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentRename) renames[0].oldName == "foo" renames[0].newName == "bar" From ba56a6517d4953ade4f1ff944b7fb3dd83a30cd1 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 04:32:26 +1000 Subject: [PATCH 211/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 29 ++++- .../ana/EditOperationAnalyzerTest.groovy | 117 ++++++++++++------ 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b2e1d1988b..ab889bc055 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -80,7 +80,10 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio break; case DELETE_VERTEX: if (editOperation.getSourceVertex().isOfType(SchemaGraph.ARGUMENT)) { - removedArgument(editOperation); + argumentDeleted(editOperation); + } + if (editOperation.getSourceVertex().isOfType(SchemaGraph.FIELD)) { + fieldDeleted(editOperation); } } } @@ -286,6 +289,28 @@ private void fieldAdded(EditOperation editOperation) { objectModification.getDetails().add(new ObjectFieldAddition(name)); } } + private void fieldDeleted(EditOperation editOperation) { + Vertex deletedField = editOperation.getSourceVertex(); + Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(deletedField); + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + Vertex object = fieldsContainerForField; + if (isObjectDeleted(object.getName())) { + return; + } + ObjectModification objectModification = getObjectModification(object.getName()); + String name = deletedField.getName(); + objectModification.getDetails().add(new ObjectFieldDeletion(name)); + }else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + if (isInterfaceDeleted(interfaze.getName())) { + return; + } + InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); + String name = deletedField.getName(); + interfaceModification.getDetails().add(new InterfaceFieldDeletion(name)); + } + } private void handleTypeVertexChanges(List editOperations) { @@ -779,7 +804,7 @@ private void deletedDirective(EditOperation editOperation) { directiveDifferences.put(directiveName, change); } - private void removedArgument(EditOperation editOperation) { + private void argumentDeleted(EditOperation editOperation) { Vertex removedArgument = editOperation.getSourceVertex(); Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(removedArgument); if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index cf50bab4db..dfa8a7183b 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -25,7 +25,7 @@ class EditOperationAnalyzerTest extends Specification { ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] === changes.objectDifferences["MyQuery"] changes.objectDifferences["Query"] instanceof ObjectModification @@ -53,7 +53,7 @@ class EditOperationAnalyzerTest extends Specification { ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences["I"] === changes.interfaceDifferences["IRenamed"] changes.interfaceDifferences["I"] instanceof InterfaceModification @@ -77,7 +77,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def implementationDeletions = (changes.objectDifferences["Query"] as ObjectModification).getDetails(ObjectInterfaceImplementationDeletion) @@ -112,7 +112,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences["Foo"] instanceof InterfaceModification def implementationDeletions = (changes.interfaceDifferences["Foo"] as InterfaceModification).getDetails(InterfaceInterfaceImplementationDeletion) @@ -132,7 +132,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification @@ -141,6 +141,41 @@ class EditOperationAnalyzerTest extends Specification { fieldRenames[0].newName == "hello2" } + def "object and interface field deleted"() { + given: + def oldSdl = ''' + type Query implements I{ + hello: String + toDelete: String + } + interface I { + hello: String + toDelete: String + } + ''' + def newSdl = ''' + type Query implements I{ + hello: String + } + interface I { + hello: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification + def oFieldDeletions = objectModification.getDetails(ObjectFieldDeletion.class) + oFieldDeletions[0].name == "toDelete" + and: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["I"] as InterfaceModification + def iFieldDeletions = interfaceModification.getDetails(InterfaceFieldDeletion.class) + iFieldDeletions[0].name == "toDelete" + + } + def "union added"() { given: def oldSdl = ''' @@ -163,7 +198,7 @@ class EditOperationAnalyzerTest extends Specification { ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionAddition (changes.unionDifferences["U"] as UnionAddition).name == "U" @@ -190,7 +225,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionDeletion (changes.unionDifferences["U"] as UnionDeletion).name == "U" @@ -223,7 +258,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["X"] === changes.unionDifferences["U"] changes.unionDifferences["U"] instanceof UnionModification @@ -255,7 +290,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionModification def unionDiff = changes.unionDifferences["U"] as UnionModification @@ -289,7 +324,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionModification def unionDiff = changes.unionDifferences["U"] as UnionModification @@ -331,7 +366,7 @@ class EditOperationAnalyzerTest extends Specification { ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionModification def unionModification = changes.unionDifferences["U"] as UnionModification @@ -364,7 +399,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.unionDifferences["U"] instanceof UnionModification def unionModification = changes.unionDifferences["U"] as UnionModification @@ -384,7 +419,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification @@ -412,7 +447,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification @@ -442,7 +477,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification @@ -471,7 +506,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification @@ -505,7 +540,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification @@ -530,7 +565,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Foo"] instanceof ObjectAddition } @@ -551,7 +586,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Foo"] instanceof ObjectDeletion (changes.objectDifferences["Foo"] as ObjectDeletion).name == "Foo" @@ -583,7 +618,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences["I"] instanceof InterfaceModification def iModification = changes.interfaceDifferences["I"] as InterfaceModification @@ -619,7 +654,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences["I"] instanceof InterfaceModification def iModification = changes.interfaceDifferences["I"] as InterfaceModification @@ -660,7 +695,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences.size() == 1 changes.interfaceDifferences["Node"] instanceof InterfaceAddition @@ -691,7 +726,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences.size() == 1 changes.interfaceDifferences["Node"] instanceof InterfaceAddition @@ -726,7 +761,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences.size() == 2 changes.interfaceDifferences["Node"] === changes.interfaceDifferences["Node2"] @@ -762,7 +797,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.interfaceDifferences.size() == 3 changes.interfaceDifferences["Node"] == changes.interfaceDifferences["Node2"] @@ -796,7 +831,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.enumDifferences["E"] === changes.enumDifferences["ERenamed"] def modification = changes.enumDifferences["E"] as EnumModification @@ -821,7 +856,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.enumDifferences["E"] instanceof EnumAddition (changes.enumDifferences["E"] as EnumAddition).getName() == "E" @@ -843,7 +878,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.enumDifferences["E"] instanceof EnumDeletion (changes.enumDifferences["E"] as EnumDeletion).getName() == "E" @@ -869,7 +904,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.enumDifferences["E"] instanceof EnumModification def enumModification = changes.enumDifferences["E"] as EnumModification @@ -895,7 +930,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.enumDifferences["E"] instanceof EnumModification def enumModification = changes.enumDifferences["E"] as EnumModification @@ -916,7 +951,7 @@ class EditOperationAnalyzerTest extends Specification { scalar E ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.scalarDifferences["E"] instanceof ScalarAddition (changes.scalarDifferences["E"] as ScalarAddition).getName() == "E" @@ -936,7 +971,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.scalarDifferences["E"] instanceof ScalarDeletion (changes.scalarDifferences["E"] as ScalarDeletion).getName() == "E" @@ -957,7 +992,7 @@ class EditOperationAnalyzerTest extends Specification { scalar Bar ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.scalarDifferences["Foo"] === changes.scalarDifferences["Bar"] def modification = changes.scalarDifferences["Foo"] as ScalarModification @@ -981,7 +1016,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.inputObjectDifferences["I"] instanceof InputObjectAddition (changes.inputObjectDifferences["I"] as InputObjectAddition).getName() == "I" @@ -1003,7 +1038,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.inputObjectDifferences["I"] instanceof InputObjectDeletion (changes.inputObjectDifferences["I"] as InputObjectDeletion).getName() == "I" @@ -1028,7 +1063,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.inputObjectDifferences["I"] === changes.inputObjectDifferences["IRenamed"] def modification = changes.inputObjectDifferences["I"] as InputObjectModification @@ -1052,7 +1087,7 @@ class EditOperationAnalyzerTest extends Specification { directive @d on FIELD ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.directiveDifferences["d"] instanceof DirectiveAddition (changes.directiveDifferences["d"] as DirectiveAddition).getName() == "d" @@ -1072,7 +1107,7 @@ class EditOperationAnalyzerTest extends Specification { } ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.directiveDifferences["d"] instanceof DirectiveDeletion (changes.directiveDifferences["d"] as DirectiveDeletion).getName() == "d" @@ -1093,7 +1128,7 @@ class EditOperationAnalyzerTest extends Specification { directive @dRenamed on FIELD ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.directiveDifferences["d"] === changes.directiveDifferences["dRenamed"] def modification = changes.directiveDifferences["d"] as DirectiveModification @@ -1116,7 +1151,7 @@ class EditOperationAnalyzerTest extends Specification { directive @d(bar:String) on FIELD ''' when: - def changes = changes(oldSdl, newSdl) + def changes = calcDiff(oldSdl, newSdl) then: changes.directiveDifferences["d"] instanceof DirectiveModification def renames = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentRename) @@ -1127,7 +1162,7 @@ class EditOperationAnalyzerTest extends Specification { } - EditOperationAnalysisResult changes( + EditOperationAnalysisResult calcDiff( String oldSdl, String newSdl ) { From 0d4b64eded11d05e21d0ba9033c52b794fe62a2f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 04:42:10 +1000 Subject: [PATCH 212/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 14 +++++++++-- .../ana/EditOperationAnalyzerTest.groovy | 23 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ab889bc055..a3f957a26d 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -281,12 +281,22 @@ private void fieldAdded(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { - if (isObjectAdded(fieldsContainerForField.getName())) { + Vertex object = fieldsContainerForField; + if (isObjectAdded(object.getName())) { return; } - ObjectModification objectModification = getObjectModification(fieldsContainerForField.getName()); + ObjectModification objectModification = getObjectModification(object.getName()); String name = field.getName(); objectModification.getDetails().add(new ObjectFieldAddition(name)); + }else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + if (isInterfaceAdded(interfaze.getName())) { + return; + } + InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); + String name = field.getName(); + interfaceModification.getDetails().add(new InterfaceFieldAddition(name)); } } private void fieldDeleted(EditOperation editOperation) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index dfa8a7183b..4c234b8442 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -526,15 +526,22 @@ class EditOperationAnalyzerTest extends Specification { intDefaultValueModified[0].newValue == '"barChanged"' } - def "field added"() { + def "object and interface field added"() { given: def oldSdl = ''' - type Query { + type Query implements I{ + hello: String + } + interface I { hello: String } ''' def newSdl = ''' - type Query { + type Query implements I{ + hello: String + newOne: String + } + interface I { hello: String newOne: String } @@ -544,8 +551,14 @@ class EditOperationAnalyzerTest extends Specification { then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification - def fieldAdded = objectModification.getDetails(ObjectFieldAddition) - fieldAdded[0].name == "newOne" + def oFieldAdded = objectModification.getDetails(ObjectFieldAddition) + oFieldAdded[0].name == "newOne" + and: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def iInterfaces = changes.interfaceDifferences["I"] as InterfaceModification + def iFieldAdded = iInterfaces.getDetails(InterfaceFieldAddition) + iFieldAdded[0].name == "newOne" + } def "object added"() { From e278bbb91392af7f54494cb2e4f384e1de210400 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 04:57:02 +1000 Subject: [PATCH 213/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 10 ++++++- .../schema/diffing/ana/SchemaDifference.java | 18 +++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 26 ++++++++++++++----- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index a3f957a26d..b46fb6f547 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -270,10 +270,18 @@ private void fieldChanged(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { - ObjectModification objectModification = getObjectModification(fieldsContainerForField.getName()); + Vertex object = fieldsContainerForField; + ObjectModification objectModification = getObjectModification(object.getName()); String oldName = editOperation.getSourceVertex().getName(); String newName = field.getName(); objectModification.getDetails().add(new ObjectFieldRename(oldName, newName)); + }else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); + String oldName = editOperation.getSourceVertex().getName(); + String newName = field.getName(); + interfaceModification.getDetails().add(new InterfaceFieldRename(oldName, newName)); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index a04a55ed62..f90209a124 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -396,6 +396,24 @@ public String getName() { return name; } } + class InterfaceFieldRename implements InterfaceModificationDetail { + private final String oldName; + private final String newName; + + public InterfaceFieldRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getNewName() { + return newName; + } + + public String getOldName() { + return oldName; + } + } + class InterfaceFieldTypeModification implements InterfaceModificationDetail { private final String fieldName; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 4c234b8442..5aec20c512 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -119,15 +119,21 @@ class EditOperationAnalyzerTest extends Specification { implementationDeletions[0].name == "FooI" } - def "field renamed"() { + def "object and interface field renamed"() { given: def oldSdl = ''' - type Query { + type Query implements I{ + hello: String + } + interface I { hello: String } ''' def newSdl = ''' - type Query { + type Query implements I{ + hello2: String + } + interface I { hello2: String } ''' @@ -136,9 +142,16 @@ class EditOperationAnalyzerTest extends Specification { then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification - def fieldRenames = objectModification.getDetails(ObjectFieldRename.class) - fieldRenames[0].oldName == "hello" - fieldRenames[0].newName == "hello2" + def oFieldRenames = objectModification.getDetails(ObjectFieldRename.class) + oFieldRenames[0].oldName == "hello" + oFieldRenames[0].newName == "hello2" + and: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["I"] as InterfaceModification + def iFieldRenames = interfaceModification.getDetails(InterfaceFieldRename.class) + iFieldRenames[0].oldName == "hello" + iFieldRenames[0].newName == "hello2" + } def "object and interface field deleted"() { @@ -1085,7 +1098,6 @@ class EditOperationAnalyzerTest extends Specification { } - def "directive added"() { given: def oldSdl = ''' From 2e51d6f1b6c5d0618276c1931316b63333ad3271 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 05:13:35 +1000 Subject: [PATCH 214/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 30 ++++++++++++++--- .../schema/diffing/ana/SchemaDifference.java | 26 +++++++++++++-- .../ana/EditOperationAnalyzerTest.groovy | 33 +++++++++++++++++++ 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b46fb6f547..e07938b38a 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -76,13 +76,14 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio case INSERT_VERTEX: if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldAdded(editOperation); + } else if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { + argumentAdded(editOperation); } break; case DELETE_VERTEX: if (editOperation.getSourceVertex().isOfType(SchemaGraph.ARGUMENT)) { argumentDeleted(editOperation); - } - if (editOperation.getSourceVertex().isOfType(SchemaGraph.FIELD)) { + } else if (editOperation.getSourceVertex().isOfType(SchemaGraph.FIELD)) { fieldDeleted(editOperation); } } @@ -275,7 +276,7 @@ private void fieldChanged(EditOperation editOperation) { String oldName = editOperation.getSourceVertex().getName(); String newName = field.getName(); objectModification.getDetails().add(new ObjectFieldRename(oldName, newName)); - }else { + } else { assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = fieldsContainerForField; InterfaceModification interfaceModification = getInterfaceModification(interfaze.getName()); @@ -296,7 +297,7 @@ private void fieldAdded(EditOperation editOperation) { ObjectModification objectModification = getObjectModification(object.getName()); String name = field.getName(); objectModification.getDetails().add(new ObjectFieldAddition(name)); - }else { + } else { assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = fieldsContainerForField; if (isInterfaceAdded(interfaze.getName())) { @@ -307,6 +308,7 @@ private void fieldAdded(EditOperation editOperation) { interfaceModification.getDetails().add(new InterfaceFieldAddition(name)); } } + private void fieldDeleted(EditOperation editOperation) { Vertex deletedField = editOperation.getSourceVertex(); Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(deletedField); @@ -318,7 +320,7 @@ private void fieldDeleted(EditOperation editOperation) { ObjectModification objectModification = getObjectModification(object.getName()); String name = deletedField.getName(); objectModification.getDetails().add(new ObjectFieldDeletion(name)); - }else { + } else { assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = fieldsContainerForField; if (isInterfaceDeleted(interfaze.getName())) { @@ -836,6 +838,24 @@ private void argumentDeleted(EditOperation editOperation) { } + private void argumentAdded(EditOperation editOperation) { + Vertex addedArgument = editOperation.getTargetVertex(); + Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(addedArgument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + Vertex field = fieldOrDirective; + Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); + if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { + Vertex object = fieldsContainerForField; + getObjectModification(object.getName()).getDetails().add(new ObjectFieldArgumentAddition(field.getName(), addedArgument.getName())); + } else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + getInterfaceModification(interfaze.getName()).getDetails().add(new InterfaceFieldArgumentAddition(field.getName(), addedArgument.getName())); + } + } + + } + private void changedEnum(EditOperation editOperation) { String oldName = editOperation.getSourceVertex().getName(); String newName = editOperation.getTargetVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index f90209a124..c313a97716 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -222,12 +222,19 @@ public String getFieldName() { } class ObjectFieldArgumentAddition implements ObjectModificationDetail { + private final String fieldName; private final String name; - public ObjectFieldArgumentAddition(String name) { + + public ObjectFieldArgumentAddition(String fieldName, String name) { + this.fieldName = fieldName; this.name = name; } + public String getFieldName() { + return fieldName; + } + public String getName() { return name; } @@ -452,26 +459,39 @@ public String getName() { } class InterfaceFieldArgumentAddition implements InterfaceModificationDetail { + private final String fieldName; private final String name; - public InterfaceFieldArgumentAddition(String name) { + public InterfaceFieldArgumentAddition(String fieldName, String name) { + this.fieldName = fieldName; this.name = name; } + public String getFieldName() { + return fieldName; + } + public String getName() { return name; } } class InterfaceFieldArgumentTypeModification implements InterfaceModificationDetail { + + private String fieldName; private final String oldType; private final String newType; - public InterfaceFieldArgumentTypeModification(String oldType, String newType) { + public InterfaceFieldArgumentTypeModification(String fieldName, String oldType, String newType) { + this.fieldName = fieldName; this.oldType = oldType; this.newType = newType; } + public String getFieldName() { + return fieldName; + } + public String getNewType() { return newType; } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5aec20c512..14035f1b71 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -441,6 +441,39 @@ class EditOperationAnalyzerTest extends Specification { typeModification[0].newType == "String!" } + def "object and interface field argument added"() { + given: + def oldSdl = ''' + type Query implements I{ + hello: String + } + interface I { + hello: String + } + ''' + def newSdl = ''' + type Query implements I{ + hello(arg: String): String + } + interface I { + hello(arg: String): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification + def objectArgumentAdded = objectModification.getDetails(ObjectFieldArgumentAddition.class); + objectArgumentAdded[0].name == "arg" + and: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["I"] as InterfaceModification + def interfaceArgumentAdded = interfaceModification.getDetails(InterfaceFieldArgumentAddition.class); + interfaceArgumentAdded[0].name == "arg" + + } + def "object and interface field argument renamed"() { given: def oldSdl = ''' From 72fc2cd405ef9d20684254754d4596ce24f913c8 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 05:16:56 +1000 Subject: [PATCH 215/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 5 ++++ .../schema/diffing/ana/SchemaDifference.java | 2 +- .../ana/EditOperationAnalyzerTest.groovy | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index e07938b38a..2d6cbcf0df 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -852,6 +852,11 @@ private void argumentAdded(EditOperation editOperation) { Vertex interfaze = fieldsContainerForField; getInterfaceModification(interfaze.getName()).getDetails().add(new InterfaceFieldArgumentAddition(field.getName(), addedArgument.getName())); } + }else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentAddition(addedArgument.getName())); + } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index c313a97716..fd2863b7b8 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -939,7 +939,7 @@ public String getName() { } - class DirectiveArgumentAddition implements ObjectModificationDetail { + class DirectiveArgumentAddition implements DirectiveModificationDetail { private final String name; public DirectiveArgumentAddition(String name) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 14035f1b71..9da5aeb6dd 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1216,6 +1216,29 @@ class EditOperationAnalyzerTest extends Specification { renames[0].oldName == "foo" renames[0].newName == "bar" + } + + def "directive argument added"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d(foo:String) on FIELD + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d"] instanceof DirectiveModification + def addition = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentAddition) + addition[0].name == "foo" + } From 7e61c2cabc8fc21ead49bb3cb13ec4f535bdbab1 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 05:27:17 +1000 Subject: [PATCH 216/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 16 ++++-- .../schema/diffing/ana/SchemaDifference.java | 11 ++++- .../ana/EditOperationAnalyzerTest.groovy | 49 ++++++++++++++++--- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 2d6cbcf0df..8930d1c089 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -825,15 +825,23 @@ private void deletedDirective(EditOperation editOperation) { } private void argumentDeleted(EditOperation editOperation) { - Vertex removedArgument = editOperation.getSourceVertex(); - Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(removedArgument); + Vertex deletedArgument = editOperation.getSourceVertex(); + Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(deletedArgument); if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { Vertex field = fieldOrDirective; Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(field); if (fieldsContainerForField.isOfType(SchemaGraph.OBJECT)) { Vertex object = fieldsContainerForField; - getObjectModification(object.getName()).getDetails().add(new ObjectFieldArgumentDeletion(field.getName(), removedArgument.getName())); + getObjectModification(object.getName()).getDetails().add(new ObjectFieldArgumentDeletion(field.getName(), deletedArgument.getName())); + } else { + assertTrue(fieldsContainerForField.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = fieldsContainerForField; + getInterfaceModification(interfaze.getName()).getDetails().add(new InterfaceFieldArgumentDeletion(field.getName(), deletedArgument.getName())); } + } else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentDeletion(deletedArgument.getName())); } } @@ -852,7 +860,7 @@ private void argumentAdded(EditOperation editOperation) { Vertex interfaze = fieldsContainerForField; getInterfaceModification(interfaze.getName()).getDetails().add(new InterfaceFieldArgumentAddition(field.getName(), addedArgument.getName())); } - }else { + } else { assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); Vertex directive = fieldOrDirective; getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentAddition(addedArgument.getName())); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index fd2863b7b8..6827b192bb 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -447,12 +447,19 @@ public String getOldType() { } class InterfaceFieldArgumentDeletion implements InterfaceModificationDetail { + private final String fieldName; private final String name; - public InterfaceFieldArgumentDeletion(String name) { + + public InterfaceFieldArgumentDeletion(String fieldName, String name) { + this.fieldName = fieldName; this.name = name; } + public String getFieldName() { + return fieldName; + } + public String getName() { return name; } @@ -926,7 +933,7 @@ interface DirectiveModificationDetail { } - class DirectiveArgumentDeletion implements ObjectModificationDetail { + class DirectiveArgumentDeletion implements DirectiveModificationDetail { private final String name; public DirectiveArgumentDeletion(String name) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 9da5aeb6dd..375bb288ca 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -510,15 +510,21 @@ class EditOperationAnalyzerTest extends Specification { } - def "object field argument removed"() { + def "object and interface field argument deleted"() { given: def oldSdl = ''' - type Query { + type Query implements I{ + hello(arg: String): String + } + interface I{ hello(arg: String): String } ''' def newSdl = ''' - type Query { + type Query implements I { + hello: String + } + interface I { hello: String } ''' @@ -527,9 +533,16 @@ class EditOperationAnalyzerTest extends Specification { then: changes.objectDifferences["Query"] instanceof ObjectModification def objectModification = changes.objectDifferences["Query"] as ObjectModification - def argumentRemoved = objectModification.getDetails(ObjectFieldArgumentDeletion.class); - argumentRemoved[0].fieldName == "hello" - argumentRemoved[0].name == "arg" + def oArgumentRemoved = objectModification.getDetails(ObjectFieldArgumentDeletion.class); + oArgumentRemoved[0].fieldName == "hello" + oArgumentRemoved[0].name == "arg" + and: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["I"] as InterfaceModification + def iArgumentRemoved = interfaceModification.getDetails(InterfaceFieldArgumentDeletion.class); + iArgumentRemoved[0].fieldName == "hello" + iArgumentRemoved[0].name == "arg" + } def "argument default value modified for Object and Interface"() { @@ -1240,6 +1253,30 @@ class EditOperationAnalyzerTest extends Specification { addition[0].name == "foo" + } + + def "directive argument deleted"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d(foo:String) on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d on FIELD + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d"] instanceof DirectiveModification + def deletion = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentDeletion) + deletion[0].name == "foo" + + } From 579b378a8345f9d1cb4ce6f8c9f127ef3326278d Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 05:50:37 +1000 Subject: [PATCH 217/294] work on higher level schema diff --- .../schema/diffing/SchemaGraphFactory.java | 17 +++++++--- .../diffing/ana/EditOperationAnalyzer.java | 7 +++- .../schema/diffing/ana/SchemaDifference.java | 13 +++++-- .../schema/diffing/SchemaDiffingTest.groovy | 34 +++++++++++++++++-- .../ana/EditOperationAnalyzerTest.groovy | 26 ++++++++++++++ 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 253703c39e..30f0709ea2 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -115,6 +115,9 @@ public TraversalControl leave(TraverserContext context) { if (SchemaGraph.INPUT_OBJECT.equals(vertex.getType())) { handleInputObject(vertex, schemaGraph, schema); } + if (SchemaGraph.DIRECTIVE.equals(vertex.getType())) { + handleDirective(vertex, schemaGraph, schema); + } } return schemaGraph; } @@ -211,10 +214,6 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit schemaGraph, GraphQLSchema graphQLSchema) { GraphQLOutputType type = fieldDefinition.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); -// Vertex dummyTypeVertex = new Vertex(SchemaGraph.DUMMY_TYPE_VERTEX, debugPrefix + String.valueOf(counter++)); -// dummyTypeVertex.setBuiltInType(fieldVertex.isBuiltInType()); -// schemaGraph.addVertex(dummyTypeVertex); -// schemaGraph.addEdge(new Edge(fieldVertex, dummyTypeVertex)); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(fieldVertex, typeVertex); typeEdge.setLabel("type=" + GraphQLTypeUtil.simplePrint(type) + ";"); @@ -227,6 +226,16 @@ private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinit } } + private void handleDirective(Vertex directive, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) { + GraphQLDirective graphQLDirective = graphQLSchema.getDirective(directive.getName()); + for (GraphQLArgument graphQLArgument : graphQLDirective.getArguments()) { + Vertex argumentVertex = schemaGraph.findTargetVertex(directive, vertex -> vertex.isOfType(SchemaGraph.ARGUMENT) && + vertex.getName().equals(graphQLArgument.getName())).get(); + handleArgument(argumentVertex, graphQLArgument, schemaGraph); + } + + } + private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) { GraphQLInputType type = graphQLArgument.getType(); GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 8930d1c089..34afe52ccf 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -531,7 +531,12 @@ private void argumentTypeChanged(EditOperation editOperation) { newDefaultValue); getInterfaceModification(objectOrInterface.getName()).getDetails().add(defaultValueModification); } - + } else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + String oldDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getSourceEdge()); + String newDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getTargetEdge()); + getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentDefaultValueModification(argument.getName(), oldDefaultValue, newDefaultValue)); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 6827b192bb..12640e07f9 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -958,15 +958,22 @@ public String getName() { } } - class DirectiveArgumentTypeModification implements ObjectModificationDetail { + class DirectiveArgumentTypeModification implements DirectiveModificationDetail { + private final String argumentName; private final String oldType; private final String newType; - public DirectiveArgumentTypeModification(String oldType, String newType) { + + public DirectiveArgumentTypeModification(String argumentName, String oldType, String newType) { + this.argumentName = argumentName; this.oldType = oldType; this.newType = newType; } + public String getArgumentName() { + return argumentName; + } + public String getNewType() { return newType; } @@ -976,7 +983,7 @@ public String getOldType() { } } - class DirectiveArgumentDefaultValueModification implements ObjectModificationDetail { + class DirectiveArgumentDefaultValueModification implements DirectiveModificationDetail { private final String argumentName; private final String oldValue; private final String newValue; diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index de36041e7c..dcacf262ed 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -1090,9 +1090,12 @@ class SchemaDiffingTest extends Specification { then: /** - * change argument, insert argument, new edge from Directive to new Argument + * change: a2 => a3 + * insert: a4 + * new edge from directive to a4 + * new edge from a4 to String */ - operations.size() == 3 + operations.size() == 4 } def "change applied argument"() { @@ -1426,6 +1429,33 @@ class SchemaDiffingTest extends Specification { operations.findAll({ it.operation == EditOperation.Operation.CHANGE_EDGE }).size() == 1 } + + def "directive argument default value changed"() { + given: + def schema1 = schema(''' + type Query { + foo: String + } + directive @d(foo:String = "A") on FIELD + ''') + def schema2 = schema(''' + type Query { + foo: String + } + directive @d(foo: String = "B") on FIELD + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + // changing the label of the edge to the type + operations.size() == 1 + operations.findAll({ it.operation == EditOperation.Operation.CHANGE_EDGE }).size() == 1 + + + } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 375bb288ca..831d7dc72c 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1277,6 +1277,32 @@ class EditOperationAnalyzerTest extends Specification { deletion[0].name == "foo" + } + + def "directive argument default value changed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d(foo:String = "A") on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d(foo: String = "B") on FIELD + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d"] instanceof DirectiveModification + def defaultValueChange = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentDefaultValueModification) + defaultValueChange[0].argumentName == "foo" + defaultValueChange[0].oldValue == '"A"' + defaultValueChange[0].newValue == '"B"' + + } From d46169dc563b96104fee0941615ad0f32b8770a6 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 06:06:08 +1000 Subject: [PATCH 218/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 59 ++++++++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 26 ++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 34afe52ccf..3aa63205cc 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -435,6 +435,31 @@ private void typeEdgeInserted(EditOperation editOperation, List e Vertex from = newEdge.getFrom(); if (from.isOfType(SchemaGraph.FIELD)) { typeEdgeInsertedForField(editOperation, editOperations, mapping); + } else if (from.isOfType(SchemaGraph.ARGUMENT)) { + typeEdgeInsertedForArgument(editOperation, editOperations, mapping); + } + + } + + private void typeEdgeInsertedForArgument(EditOperation editOperation, List editOperations, Mapping mapping) { + Vertex argument = editOperation.getTargetEdge().getFrom(); + Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + + } else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + if (isDirectiveAdded(directive.getName())) { + return; + } + if (isArgumentNewForExistingDirective(directive.getName(), argument.getName())) { + return; + } + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + EditOperation deletedTypeEdgeOperation = findDeletedEdge(argument, editOperations, mapping); + String oldType = getTypeFromEdgeLabel(deletedTypeEdgeOperation.getSourceEdge()); + DirectiveArgumentTypeModification directiveArgumentTypeModification = new DirectiveArgumentTypeModification(argument.getName(), oldType, newType); + getDirectiveModification(directive.getName()).getDetails().add(directiveArgumentTypeModification); } } @@ -502,11 +527,11 @@ private void typeEdgeChanged(EditOperation editOperation) { if (from.isOfType(SchemaGraph.FIELD)) { fieldTypeChanged(editOperation); } else if (from.isOfType(SchemaGraph.ARGUMENT)) { - argumentTypeChanged(editOperation); + argumentTypeOrDefaultValueChanged(editOperation); } } - private void argumentTypeChanged(EditOperation editOperation) { + private void argumentTypeOrDefaultValueChanged(EditOperation editOperation) { Edge targetEdge = editOperation.getTargetEdge(); Vertex argument = targetEdge.getFrom(); Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); @@ -534,10 +559,22 @@ private void argumentTypeChanged(EditOperation editOperation) { } else { assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); Vertex directive = fieldOrDirective; + String oldDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getSourceEdge()); String newDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getTargetEdge()); - getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentDefaultValueModification(argument.getName(), oldDefaultValue, newDefaultValue)); + if (!oldDefaultValue.equals(newDefaultValue)) { + getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentDefaultValueModification(argument.getName(), oldDefaultValue, newDefaultValue)); + } + + String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + + if (!oldType.equals(newType)) { + getDirectiveModification(directive.getName()).getDetails().add(new DirectiveArgumentTypeModification(argument.getName(), oldType, newType)); + + } } + } private void fieldTypeChanged(EditOperation editOperation) { @@ -629,6 +666,10 @@ private void newInterfaceAddedToInterfaceOrObject(Edge newEdge) { } + private boolean isDirectiveAdded(String name) { + return directiveDifferences.containsKey(name) && directiveDifferences.get(name) instanceof DirectiveAddition; + } + private boolean isObjectAdded(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectAddition; } @@ -649,6 +690,18 @@ private boolean isEnumAdded(String name) { return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumAddition; } + private boolean isArgumentNewForExistingDirective(String directiveName, String argumentName) { + if (!directiveDifferences.containsKey(directiveName)) { + return false; + } + if (!(directiveDifferences.get(directiveName) instanceof DirectiveModification)) { + return false; + } + DirectiveModification directiveModification = (DirectiveModification) directiveDifferences.get(directiveName); + List newArgs = directiveModification.getDetails(DirectiveArgumentAddition.class); + return newArgs.stream().anyMatch(detail -> detail.getName().equals(argumentName)); + } + private boolean isFieldNewForExistingObject(String objectName, String fieldName) { if (!objectDifferences.containsKey(objectName)) { return false; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 831d7dc72c..5c4bf217a7 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1303,6 +1303,32 @@ class EditOperationAnalyzerTest extends Specification { defaultValueChange[0].newValue == '"B"' + } + + def "directive argument type changed completely"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d(foo:String) on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d(foo: ID) on FIELD + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d"] instanceof DirectiveModification + def argTypeModification = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentTypeModification) + argTypeModification[0].argumentName == "foo" + argTypeModification[0].oldType == 'String' + argTypeModification[0].newType == 'ID' + + } From a22eb44f06076c97667ace22df8b2526a72602a5 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 28 Oct 2022 06:32:01 +1000 Subject: [PATCH 219/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 131 ++++++++++++++++-- .../schema/diffing/ana/SchemaDifference.java | 23 ++- .../ana/EditOperationAnalyzerTest.groovy | 104 +++++++++++++- 3 files changed, 240 insertions(+), 18 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 3aa63205cc..34724b5e91 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -445,7 +445,53 @@ private void typeEdgeInsertedForArgument(EditOperation editOperation, List detail.getName().equals(argumentName)); } + private boolean isArgumentNewForExistingObjectField(String objectName, String fieldName, String argumentName) { + if (!objectDifferences.containsKey(objectName)) { + return false; + } + if (!(objectDifferences.get(objectName) instanceof ObjectModification)) { + return false; + } + // finding out if the field was just added + ObjectModification objectModification = (ObjectModification) objectDifferences.get(objectName); + List newFields = objectModification.getDetails(ObjectFieldAddition.class); + boolean newField = newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + if (newField) { + return false; + } + // now finding out if the argument is new + List newArgs = objectModification.getDetails(ObjectFieldArgumentAddition.class); + return newArgs.stream().anyMatch(detail -> detail.getFieldName().equals(fieldName) && detail.getName().equals(argumentName)); + } + + private boolean isArgumentNewForExistingInterfaceField(String objectName, String fieldName, String argumentName) { + if (!interfaceDifferences.containsKey(objectName)) { + return false; + } + if (!(interfaceDifferences.get(objectName) instanceof InterfaceModification)) { + return false; + } + // finding out if the field was just added + InterfaceModification interfaceModification = (InterfaceModification) interfaceDifferences.get(objectName); + List newFields = interfaceModification.getDetails(InterfaceFieldAddition.class); + boolean newField = newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + if (newField) { + return false; + } + // now finding out if the argument is new + List newArgs = interfaceModification.getDetails(InterfaceFieldArgumentAddition.class); + return newArgs.stream().anyMatch(detail -> detail.getFieldName().equals(fieldName) && detail.getName().equals(argumentName)); + } + private boolean isFieldNewForExistingObject(String objectName, String fieldName) { if (!objectDifferences.containsKey(objectName)) { return false; diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 12640e07f9..d3f1e2e8f0 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -241,10 +241,14 @@ public String getName() { } class ObjectFieldArgumentTypeModification implements ObjectModificationDetail { + private final String fieldName; + private final String argumentName; private final String oldType; private final String newType; - public ObjectFieldArgumentTypeModification(String oldType, String newType) { + public ObjectFieldArgumentTypeModification(String fieldName, String argumentName, String oldType, String newType) { + this.fieldName = fieldName; + this.argumentName = argumentName; this.oldType = oldType; this.newType = newType; } @@ -256,6 +260,14 @@ public String getNewType() { public String getOldType() { return oldType; } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } } class ObjectFieldArgumentDefaultValueModification implements ObjectModificationDetail { @@ -486,11 +498,13 @@ public String getName() { class InterfaceFieldArgumentTypeModification implements InterfaceModificationDetail { private String fieldName; + private final String argumentName; private final String oldType; private final String newType; - public InterfaceFieldArgumentTypeModification(String fieldName, String oldType, String newType) { + public InterfaceFieldArgumentTypeModification(String fieldName, String argumentName, String oldType, String newType) { this.fieldName = fieldName; + this.argumentName = argumentName; this.oldType = oldType; this.newType = newType; } @@ -506,6 +520,11 @@ public String getNewType() { public String getOldType() { return oldType; } + + public String getArgumentName() { + return argumentName; + } + } class InterfaceFieldArgumentDefaultValueModification implements InterfaceModificationDetail { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5c4bf217a7..b8a1a2d19b 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -585,6 +585,86 @@ class EditOperationAnalyzerTest extends Specification { intDefaultValueModified[0].newValue == '"barChanged"' } + def "object and interface argument type changed completely"() { + given: + def oldSdl = ''' + type Query implements Foo { + foo(arg: String ): String + } + interface Foo { + foo(arg: String): String + } + + ''' + def newSdl = ''' + type Query implements Foo { + foo(arg: Int!): String + } + interface Foo { + foo(arg: Int!): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification + def objDefaultValueModified = objectModification.getDetails(ObjectFieldArgumentTypeModification.class); + objDefaultValueModified[0].fieldName == "foo" + objDefaultValueModified[0].argumentName == "arg" + objDefaultValueModified[0].oldType == 'String' + objDefaultValueModified[0].newType == 'Int!' + and: + changes.interfaceDifferences["Foo"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["Foo"] as InterfaceModification + def intDefaultValueModified = interfaceModification.getDetails(InterfaceFieldArgumentTypeModification.class); + intDefaultValueModified[0].fieldName == "foo" + intDefaultValueModified[0].argumentName == "arg" + intDefaultValueModified[0].oldType == 'String' + intDefaultValueModified[0].newType == 'Int!' + } + + def "object and interface argument type changed wrapping type"() { + given: + def oldSdl = ''' + type Query implements Foo { + foo(arg: String ): String + } + interface Foo { + foo(arg: String): String + } + + ''' + def newSdl = ''' + type Query implements Foo { + foo(arg: String!): String + } + interface Foo { + foo(arg: String!): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def objectModification = changes.objectDifferences["Query"] as ObjectModification + def objDefaultValueModified = objectModification.getDetails(ObjectFieldArgumentTypeModification.class); + objDefaultValueModified[0].fieldName == "foo" + objDefaultValueModified[0].argumentName == "arg" + objDefaultValueModified[0].oldType == 'String' + objDefaultValueModified[0].newType == 'String!' + and: + changes.interfaceDifferences["Foo"] instanceof InterfaceModification + def interfaceModification = changes.interfaceDifferences["Foo"] as InterfaceModification + def intDefaultValueModified = interfaceModification.getDetails(InterfaceFieldArgumentTypeModification.class); + intDefaultValueModified[0].fieldName == "foo" + intDefaultValueModified[0].argumentName == "arg" + intDefaultValueModified[0].oldType == 'String' + intDefaultValueModified[0].newType == 'String!' + } + def "object and interface field added"() { given: def oldSdl = ''' @@ -1327,8 +1407,30 @@ class EditOperationAnalyzerTest extends Specification { argTypeModification[0].argumentName == "foo" argTypeModification[0].oldType == 'String' argTypeModification[0].newType == 'ID' + } - + def "directive argument wrapping type changed"() { + given: + def oldSdl = ''' + type Query { + foo: String + } + directive @d(foo:String) on FIELD + ''' + def newSdl = ''' + type Query { + foo: String + } + directive @d(foo: [String]!) on FIELD + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d"] instanceof DirectiveModification + def argTypeModification = (changes.directiveDifferences["d"] as DirectiveModification).getDetails(DirectiveArgumentTypeModification) + argTypeModification[0].argumentName == "foo" + argTypeModification[0].oldType == 'String' + argTypeModification[0].newType == '[String]!' } From e79ca1fa062ca03b0319ee8af4135dd951932750 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 29 Oct 2022 07:57:49 +1000 Subject: [PATCH 220/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 29 +++++++++ .../schema/diffing/ana/SchemaDifference.java | 64 ++++++++++++++++++- .../schema/diffing/SchemaDiffingTest.groovy | 56 ++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 25 ++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 34724b5e91..1e007fa86b 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -93,6 +93,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleUnionMemberChanges(editOperations, mapping); handleEnumValuesChanges(editOperations, mapping); handleArgumentChanges(editOperations, mapping); + handleAppliedDirectives(editOperations, mapping); return new EditOperationAnalysisResult( objectDifferences, @@ -104,6 +105,34 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio directiveDifferences); } + private void handleAppliedDirectives(List editOperations, Mapping mapping) { + + for (EditOperation editOperation : editOperations) { + switch (editOperation.getOperation()) { + case INSERT_VERTEX: + if(editOperation.getTargetVertex().isOfType(SchemaGraph.APPLIED_DIRECTIVE)) { + appliedDirectiveAdded(editOperation); + } + break; + } + } + + } + + private void appliedDirectiveAdded(EditOperation editOperation) { + Vertex appliedDirective = editOperation.getTargetVertex(); + Vertex container = newSchemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + if (container.isOfType(SchemaGraph.FIELD)) { + Vertex field = container; + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + AppliedDirectiveFieldAddition appliedDirectiveFieldAddition = new AppliedDirectiveFieldAddition(field.getName(), appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveFieldAddition); + } + } + } + private void handleTypeChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { Edge newEdge = editOperation.getTargetEdge(); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index d3f1e2e8f0..3b157b92f1 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -89,6 +89,18 @@ public List getDetails() { public List getDetails(Class clazz) { return (List) FpKit.filterList(details, clazz::isInstance); } + + public String getOldName() { + return oldName; + } + + public String getNewName() { + return newName; + } + + public boolean isRenamed() { + return renamed; + } } interface ObjectModificationDetail { @@ -415,6 +427,7 @@ public String getName() { return name; } } + class InterfaceFieldRename implements InterfaceModificationDetail { private final String oldName; private final String newName; @@ -557,6 +570,7 @@ public String getArgumentName() { return argumentName; } } + class InterfaceFieldArgumentRename implements InterfaceModificationDetail { private final String oldName; private final String newName; @@ -921,7 +935,8 @@ public DirectiveModification(String oldName, String newName) { this.newName = newName; this.nameChanged = oldName.equals(newName); } - public DirectiveModification( String newName) { + + public DirectiveModification(String newName) { this.oldName = newName; this.newName = newName; this.nameChanged = false; @@ -1044,5 +1059,52 @@ public String getOldName() { } } + //------Applied Directives + interface AppliedDirectiveDifference { + + } + + class AppliedDirectiveFieldAddition implements ObjectModificationDetail { + private final String fieldName; + private final String name; + + public AppliedDirectiveFieldAddition(String fieldName, String name) { + this.fieldName = fieldName; + this.name = name; + } + + public String getFieldName() { + return fieldName; + } + + public String getName() { + return name; + } + } + + class AppliedDirectiveDeletion { + + } + + class AppliedDirectiveRenamed { + + } + + class AppliedDirectiveArgumentAddition { + + } + + class AppliedDirectiveArgumentDeletion { + + } + + class AppliedDirectiveArgumentValueModification { + + } + + class AppliedDirectiveArgumentNameModification { + + } + } diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index dcacf262ed..06360c906f 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -5,6 +5,7 @@ import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchemaElement import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer +import graphql.schema.diffing.ana.SchemaDifference import graphql.util.TraversalControl import graphql.util.TraverserContext import org.junit.Ignore @@ -1456,6 +1457,61 @@ class SchemaDiffingTest extends Specification { } + + def "object applied directive argument change"() { + given: + def schema1 = schema(''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo") + } + + ''') + def schema2 = schema(''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "bar") + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 1 + } + + def "object applied directive rename"() { + given: + def schema1 = schema(''' + directive @d1(arg:String) on FIELD_DEFINITION + directive @d2(arg:String) on FIELD_DEFINITION + + type Query { + foo: String @d1(arg: "foo") + } + + ''') + def schema2 = schema(''' + directive @d1(arg:String) on FIELD_DEFINITION + directive @d2(arg:String) on FIELD_DEFINITION + + type Query { + foo: String @d2(arg: "foo") + } + ''') + + when: + def operations = new SchemaDiffing().diffGraphQLSchema(schema1, schema2) + operations.each { println it } + + then: + operations.size() == 1 + } + } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index b8a1a2d19b..d03571d972 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1433,6 +1433,31 @@ class EditOperationAnalyzerTest extends Specification { argTypeModification[0].newType == '[String]!' } + def "object added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveFieldAddition) + appliedDirective[0].fieldName == "foo" + appliedDirective[0].name == "d" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From bb4a3b2be565b457c5a8cd353af3351882372aa2 Mon Sep 17 00:00:00 2001 From: Aaron Paterson Date: Fri, 28 Oct 2022 21:38:38 -0600 Subject: [PATCH 221/294] Parameterized introspection queries (#2993) * use ast builders and stub test * remove main and add test * restore jvm check * revert test spacing * fix test * revers build.gradle * use spec specifiedByURL capitalization * update another specifiedByURL test * refactor introspection query builder, restore and deprecate __Type.specifiedByUrl * test that AST printed introspection query is equivalent to original string --- .../graphql/introspection/Introspection.java | 12 +- .../introspection/IntrospectionQuery.java | 103 +---- .../IntrospectionQueryBuilder.java | 356 ++++++++++++++++++ src/test/groovy/graphql/GraphQLTest.groovy | 4 +- .../introspection/IntrospectionTest.groovy | 187 +++++++++ 5 files changed, 555 insertions(+), 107 deletions(-) create mode 100644 src/main/java/graphql/introspection/IntrospectionQueryBuilder.java diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index 9db9414bc8..15c41f3a77 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -90,7 +90,7 @@ public enum TypeKind { public static final GraphQLEnumType __TypeKind = GraphQLEnumType.newEnum() .name("__TypeKind") .description("An enum describing what kind of type a given __Type is") - .value("SCALAR", TypeKind.SCALAR, "Indicates this type is a scalar. 'specifiedByUrl' is a valid field") + .value("SCALAR", TypeKind.SCALAR, "Indicates this type is a scalar. 'specifiedByURL' is a valid field") .value("OBJECT", TypeKind.OBJECT, "Indicates this type is an object. `fields` and `interfaces` are valid fields.") .value("INTERFACE", TypeKind.INTERFACE, "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.") .value("UNION", TypeKind.UNION, "Indicates this type is a union. `possibleTypes` is a valid field.") @@ -398,8 +398,13 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .name("ofType") .type(typeRef("__Type"))) .field(newFieldDefinition() - .name("specifiedByUrl") + .name("specifiedByURL") .type(GraphQLString)) + .field(newFieldDefinition() + .name("specifiedByUrl") + .type(GraphQLString) + .deprecate("see `specifiedByURL`") + ) .build(); static { @@ -412,7 +417,8 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, register(__Type, "ofType", OfTypeFetcher); register(__Type, "name", nameDataFetcher); register(__Type, "description", descriptionDataFetcher); - register(__Type, "specifiedByUrl", specifiedByUrlDataFetcher); + register(__Type, "specifiedByURL", specifiedByUrlDataFetcher); + register(__Type, "specifiedByUrl", specifiedByUrlDataFetcher); // note that this field is deprecated } diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index f93617951f..4d372fe992 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -4,106 +4,5 @@ @PublicApi public interface IntrospectionQuery { - - String INTROSPECTION_QUERY = "\n" + - " query IntrospectionQuery {\n" + - " __schema {\n" + - " queryType { name }\n" + - " mutationType { name }\n" + - " subscriptionType { name }\n" + - " types {\n" + - " ...FullType\n" + - " }\n" + - " directives {\n" + - " name\n" + - " description\n" + - " locations\n" + - " args(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " isRepeatable\n" + - " }\n" + - " }\n" + - " }\n" + - "\n" + - " fragment FullType on __Type {\n" + - " kind\n" + - " name\n" + - " description\n" + - " fields(includeDeprecated: true) {\n" + - " name\n" + - " description\n" + - " args(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " type {\n" + - " ...TypeRef\n" + - " }\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - " inputFields(includeDeprecated: true) {\n" + - " ...InputValue\n" + - " }\n" + - " interfaces {\n" + - " ...TypeRef\n" + - " }\n" + - " enumValues(includeDeprecated: true) {\n" + - " name\n" + - " description\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - " possibleTypes {\n" + - " ...TypeRef\n" + - " }\n" + - " }\n" + - "\n" + - " fragment InputValue on __InputValue {\n" + - " name\n" + - " description\n" + - " type { ...TypeRef }\n" + - " defaultValue\n" + - " isDeprecated\n" + - " deprecationReason\n" + - " }\n" + - "\n" + - // - // The depth of the types is actually an arbitrary decision. It could be any depth in fact. This depth - // was taken from GraphIQL https://github.com/graphql/graphiql/blob/master/src/utility/introspectionQueries.js - // which uses 7 levels and hence could represent a type like say [[[[[Float!]]]]] - // - "fragment TypeRef on __Type {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " ofType {\n" + - " kind\n" + - " name\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - "\n"; + String INTROSPECTION_QUERY = IntrospectionQueryBuilder.build(); } diff --git a/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java new file mode 100644 index 0000000000..cece9aeec1 --- /dev/null +++ b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java @@ -0,0 +1,356 @@ +package graphql.introspection; + +import com.google.common.collect.ImmutableList; +import graphql.language.Argument; +import graphql.language.AstPrinter; +import graphql.language.BooleanValue; +import graphql.language.Document; +import graphql.language.Field; +import graphql.language.FragmentDefinition; +import graphql.language.FragmentSpread; +import graphql.language.OperationDefinition; +import graphql.language.SelectionSet; +import graphql.language.TypeName; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class IntrospectionQueryBuilder { + public static class Options { + + private final boolean descriptions; + + private final boolean specifiedByUrl; + + private final boolean directiveIsRepeatable; + + private final boolean schemaDescription; + + private final boolean inputValueDeprecation; + + private final int typeRefFragmentDepth; + + private Options(boolean descriptions, + boolean specifiedByUrl, + boolean directiveIsRepeatable, + boolean schemaDescription, + boolean inputValueDeprecation, + int typeRefFragmentDepth) { + this.descriptions = descriptions; + this.specifiedByUrl = specifiedByUrl; + this.directiveIsRepeatable = directiveIsRepeatable; + this.schemaDescription = schemaDescription; + this.inputValueDeprecation = inputValueDeprecation; + this.typeRefFragmentDepth = typeRefFragmentDepth; + } + + public boolean isDescriptions() { + return descriptions; + } + + public boolean isSpecifiedByUrl() { + return specifiedByUrl; + } + + public boolean isDirectiveIsRepeatable() { + return directiveIsRepeatable; + } + + public boolean isSchemaDescription() { + return schemaDescription; + } + + public boolean isInputValueDeprecation() { + return inputValueDeprecation; + } + + public int getTypeRefFragmentDepth() { + return typeRefFragmentDepth; + } + + public static Options defaultOptions() { + return new Options( + true, + false, + true, + false, + true, + 7 + ); + } + + /** + * This will allow you to include description fields in the introspection query + * + * @param flag whether to include them + * + * @return options + */ + public Options descriptions(boolean flag) { + return new Options(flag, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `specifiedByURL` field for scalar types in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options specifiedByUrl(boolean flag) { + return new Options(this.descriptions, + flag, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `isRepeatable` field for directives in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options directiveIsRepeatable(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + flag, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include the `description` field for the schema type in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options schemaDescription(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + flag, + this.inputValueDeprecation, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to include deprecated input fields in the introspection query. + * + * @param flag whether to include them + * + * @return options + */ + public Options inputValueDeprecation(boolean flag) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + flag, + this.typeRefFragmentDepth); + } + + /** + * This will allow you to control the depth of the `TypeRef` fragment in the introspection query. + * + * @param typeRefFragmentDepth the depth of the `TypeRef` fragment. + * + * @return options + */ + public Options typeRefFragmentDepth(int typeRefFragmentDepth) { + return new Options(this.descriptions, + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + typeRefFragmentDepth); + } + } + + private static List filter(T... args) { + return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static String build() { + return build(Options.defaultOptions()); + } + + public static String build(Options options) { + SelectionSet schemaSelectionSet = SelectionSet.newSelectionSet().selections( filter( + options.schemaDescription ? Field.newField("description").build() : null, + Field.newField("queryType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("mutationType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("subscriptionType", SelectionSet.newSelectionSet() + .selection( Field.newField("name").build() ) + .build() + ) + .build(), + Field.newField("types", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("FullType").build() ) + .build() + ) + .build(), + Field.newField("directives", SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("locations").build(), + Field.newField("args") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + options.directiveIsRepeatable ? Field.newField("isRepeatable").build() : null + ) ) + .build() + ) + .build() + ) + ).build(); + + SelectionSet fullTypeSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + options.specifiedByUrl ? Field.newField("specifiedByURL").build() : null, + Field.newField("fields") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("args") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ).build() + ) + .build(), + Field.newField("inputFields") + .arguments( filter( + options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + ) ) + .selectionSet( SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + .build() + ) + .build(), + Field.newField("interfaces", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("enumValues") + .arguments( ImmutableList.of( + Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() + ) ) + .selectionSet( SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("isDeprecated").build(), + Field.newField("deprecationReason").build() + ) ) + .build() + ) + .build(), + Field.newField("possibleTypes", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build() + ) ).build(); + + SelectionSet inputValueSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("name").build(), + options.descriptions ? Field.newField("description").build() : null, + Field.newField("type", SelectionSet.newSelectionSet() + .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + .build() + ) + .build(), + Field.newField("defaultValue").build(), + options.inputValueDeprecation ? Field.newField("isDeprecated").build() : null, + options.inputValueDeprecation ? Field.newField("deprecationReason").build() : null + ) ).build(); + + SelectionSet typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build() + ) ).build(); + + for(int i=options.typeRefFragmentDepth; i>0; i-=1) { + typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( + Field.newField("kind").build(), + Field.newField("name").build(), + Field.newField("ofType", typeRefSelectionSet).build() + ) ).build(); + } + + Document query = Document.newDocument() + .definition( OperationDefinition.newOperationDefinition() + .operation(OperationDefinition.Operation.QUERY) + .name("IntrospectionQuery") + .selectionSet( SelectionSet.newSelectionSet() + .selection( Field.newField("__schema", schemaSelectionSet).build() ) + .build() + ) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("FullType") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(fullTypeSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("InputValue") + .typeCondition( TypeName.newTypeName().name("__InputValue").build() ) + .selectionSet(inputValueSelectionSet) + .build() + ) + .definition( FragmentDefinition.newFragmentDefinition() + .name("TypeRef") + .typeCondition( TypeName.newTypeName().name("__Type").build() ) + .selectionSet(typeRefSelectionSet) + .build() + ) + .build(); + + return AstPrinter.printAst(query); + } +} diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index 505350e74b..ef2d4e19f2 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -1251,10 +1251,10 @@ many lines'''] GraphQLSchema schema = TestUtil.schema('type Query {foo: MyScalar} scalar MyScalar @specifiedBy(url:"myUrl")') when: - def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByUrl}}').getData() + def result = GraphQL.newGraphQL(schema).build().execute('{__type(name: "MyScalar") {name specifiedByURL}}').getData() then: - result == [__type: [name: "MyScalar", specifiedByUrl: "myUrl"]] + result == [__type: [name: "MyScalar", specifiedByURL: "myUrl"]] } def "test DFR and CF"() { diff --git a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy index 18f259cbed..712ac8048c 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionTest.groovy @@ -435,4 +435,191 @@ class IntrospectionTest extends Specification { graphql.execute(query).data == [__type: [fields: [[args: [[defaultValue: '{inputField : "foo"}']]]]]] } + + def "test AST printed introspection query is equivalent to original string"() { + when: + def oldIntrospectionQuery = "\n" + + " query IntrospectionQuery {\n" + + " __schema {\n" + + " queryType { name }\n" + + " mutationType { name }\n" + + " subscriptionType { name }\n" + + " types {\n" + + " ...FullType\n" + + " }\n" + + " directives {\n" + + " name\n" + + " description\n" + + " locations\n" + + " args(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " isRepeatable\n" + + " }\n" + + " }\n" + + " }\n" + + "\n" + + " fragment FullType on __Type {\n" + + " kind\n" + + " name\n" + + " description\n" + + " fields(includeDeprecated: true) {\n" + + " name\n" + + " description\n" + + " args(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " type {\n" + + " ...TypeRef\n" + + " }\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + " inputFields(includeDeprecated: true) {\n" + + " ...InputValue\n" + + " }\n" + + " interfaces {\n" + + " ...TypeRef\n" + + " }\n" + + " enumValues(includeDeprecated: true) {\n" + + " name\n" + + " description\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + " possibleTypes {\n" + + " ...TypeRef\n" + + " }\n" + + " }\n" + + "\n" + + " fragment InputValue on __InputValue {\n" + + " name\n" + + " description\n" + + " type { ...TypeRef }\n" + + " defaultValue\n" + + " isDeprecated\n" + + " deprecationReason\n" + + " }\n" + + "\n" + + // + // The depth of the types is actually an arbitrary decision. It could be any depth in fact. This depth + // was taken from GraphIQL https://github.com/graphql/graphiql/blob/master/src/utility/introspectionQueries.js + // which uses 7 levels and hence could represent a type like say [[[[[Float!]]]]] + // + "fragment TypeRef on __Type {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " ofType {\n" + + " kind\n" + + " name\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "\n" + + def newIntrospectionQuery = IntrospectionQuery.INTROSPECTION_QUERY; + + then: + oldIntrospectionQuery.replaceAll("\\s+","").equals( + newIntrospectionQuery.replaceAll("\\s+","") + ) + } + + def "test parameterized introspection queries"() { + def spec = ''' + scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") + + directive @repeatableDirective(arg: String) repeatable on FIELD + + """schema description""" + schema { + query: Query + } + + directive @someDirective( + deprecatedArg : String @deprecated + notDeprecatedArg : String + ) repeatable on FIELD + + type Query { + """notDeprecated root field description""" + notDeprecated(arg : InputType @deprecated, notDeprecatedArg : InputType) : Enum + tenDimensionalList : [[[[[[[[[[String]]]]]]]]]] + } + enum Enum { + RED @deprecated + BLUE + } + input InputType { + inputField : String @deprecated + } + ''' + + def graphQL = TestUtil.graphQL(spec).build() + + def parseExecutionResult = { + [ + it.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "notDeprecated"}["description"] != null, // descriptions is true + it.data["__schema"]["types"].find{it["name"] == "UUID"}["specifiedByURL"] != null, // specifiedByUrl is true + it.data["__schema"]["directives"].find{it["name"] == "repeatableDirective"}["isRepeatable"] != null, // directiveIsRepeatable is true + it.data["__schema"]["description"] != null, // schemaDescription is true + it.data["__schema"]["types"].find { it['name'] == 'InputType' }["inputFields"].find({ it["name"] == "inputField" }) != null // inputValueDeprecation is true + ] + } + + when: + def allFalseExecutionResult = graphQL.execute( + IntrospectionQueryBuilder.build( + IntrospectionQueryBuilder.Options.defaultOptions() + .descriptions(false) + .specifiedByUrl(false) + .directiveIsRepeatable(false) + .schemaDescription(false) + .inputValueDeprecation(false) + .typeRefFragmentDepth(5) + ) + ) + then: + !parseExecutionResult(allFalseExecutionResult).any() + allFalseExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 5 + + when: + def allTrueExecutionResult = graphQL.execute( + IntrospectionQueryBuilder.build( + IntrospectionQueryBuilder.Options.defaultOptions() + .descriptions(true) + .specifiedByUrl(true) + .directiveIsRepeatable(true) + .schemaDescription(true) + .inputValueDeprecation(true) + .typeRefFragmentDepth(7) + ) + ) + then: + parseExecutionResult(allTrueExecutionResult).every() + allTrueExecutionResult.data["__schema"]["types"].find{it["name"] == "Query"}["fields"].find{it["name"] == "tenDimensionalList"}["type"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"]["ofType"] == null // typeRefFragmentDepth is 7 + } } From 8951a75af29ebd978faf2156d12fca2a0bb3053a Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 30 Oct 2022 08:00:25 +1000 Subject: [PATCH 222/294] higher level diff work --- .../diffing/ana/EditOperationAnalyzer.java | 7 +- .../schema/diffing/ana/SchemaDifference.java | 83 ++++++++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 5 +- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 1e007fa86b..ed00b7139d 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -41,8 +41,6 @@ public class EditOperationAnalyzer { private SchemaGraph oldSchemaGraph; private SchemaGraph newSchemaGraph; -// private List changes = new ArrayList<>(); - private Map objectDifferences = new LinkedHashMap<>(); private Map interfaceDifferences = new LinkedHashMap<>(); private Map unionDifferences = new LinkedHashMap<>(); @@ -127,8 +125,9 @@ private void appliedDirectiveAdded(EditOperation editOperation) { Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { Vertex object = interfaceOrObjective; - AppliedDirectiveFieldAddition appliedDirectiveFieldAddition = new AppliedDirectiveFieldAddition(field.getName(), appliedDirective.getName()); - getObjectModification(object.getName()).getDetails().add(appliedDirectiveFieldAddition); + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(),field.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 3b157b92f1..72555a77b2 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1064,22 +1064,97 @@ interface AppliedDirectiveDifference { } - class AppliedDirectiveFieldAddition implements ObjectModificationDetail { + /** + * SCHEMA, + * SCALAR, + * OBJECT, + * FIELD_DEFINITION, + * ARGUMENT_DEFINITION, + * INTERFACE, + * UNION, + * ENUM, + * ENUM_VALUE, + * INPUT_OBJECT, + * INPUT_FIELD_DEFINITION + */ + + interface AppliedDirectiveLocationDetail { + + } + class AppliedDirectiveObjectFieldLocation implements AppliedDirectiveLocationDetail{ + private final String objectName; private final String fieldName; - private final String name; - public AppliedDirectiveFieldAddition(String fieldName, String name) { + public AppliedDirectiveObjectFieldLocation(String objectName, String fieldName) { + this.objectName = objectName; this.fieldName = fieldName; - this.name = name; } public String getFieldName() { return fieldName; } + public String getObjectName() { + return objectName; + } + } + class AppliedDirectiveInterfaceFieldLocation implements AppliedDirectiveLocationDetail { + private final String interfaceName; + private final String fieldName; + + public AppliedDirectiveInterfaceFieldLocation(String interfaceName, String fieldName) { + this.interfaceName = interfaceName; + this.fieldName = fieldName; + } + } + + class AppliedDirectiveScalarLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveSchemaLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveObjectLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveInterfaceLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveEnumLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveInputObjectLocation implements AppliedDirectiveLocationDetail{ + + } + class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocationDetail{ + + } + + class AppliedDirectiveAddition implements ObjectModificationDetail { + private final AppliedDirectiveLocationDetail locationDetail; + private final String name; + + public AppliedDirectiveAddition(AppliedDirectiveLocationDetail locationDetail, String name) { + this.locationDetail = locationDetail; + this.name = name; + } + public String getName() { return name; } + + public AppliedDirectiveLocationDetail getLocationDetail() { + return locationDetail; + } } class AppliedDirectiveDeletion { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index d03571d972..a0350624d8 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1453,8 +1453,9 @@ class EditOperationAnalyzerTest extends Specification { def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification - def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveFieldAddition) - appliedDirective[0].fieldName == "foo" + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" appliedDirective[0].name == "d" } From 497b05d460a38a51dfe96e4b4eb0ed43ceaa5b40 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 30 Oct 2022 08:33:50 +1000 Subject: [PATCH 223/294] higher level diff work --- .../diffing/ana/EditOperationAnalyzer.java | 88 ++++++++++++++++++- .../schema/diffing/ana/SchemaDifference.java | 86 ++++++++++++------ .../ana/EditOperationAnalyzerTest.groovy | 84 +++++++++++++++++- 3 files changed, 228 insertions(+), 30 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ed00b7139d..1b985a198f 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -15,20 +15,68 @@ import java.util.Map; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.ana.SchemaDifference.*; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveAddition; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentAddition; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentDefaultValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentRename; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentTypeModification; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveDifference; +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveModification; +import static graphql.schema.diffing.ana.SchemaDifference.EnumAddition; +import static graphql.schema.diffing.ana.SchemaDifference.EnumDeletion; import static graphql.schema.diffing.ana.SchemaDifference.EnumDifference; +import static graphql.schema.diffing.ana.SchemaDifference.EnumModification; +import static graphql.schema.diffing.ana.SchemaDifference.EnumValueAddition; +import static graphql.schema.diffing.ana.SchemaDifference.EnumValueDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectAddition; import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDeletion; import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectModification; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceAddition; import static graphql.schema.diffing.ana.SchemaDifference.InterfaceDeletion; import static graphql.schema.diffing.ana.SchemaDifference.InterfaceDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldAddition; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentAddition; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentDefaultValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentRename; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentTypeModification; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldRename; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldTypeModification; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceInterfaceImplementationAddition; +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceInterfaceImplementationDeletion; import static graphql.schema.diffing.ana.SchemaDifference.InterfaceModification; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectAddition; import static graphql.schema.diffing.ana.SchemaDifference.ObjectDeletion; import static graphql.schema.diffing.ana.SchemaDifference.ObjectDifference; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldAddition; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentAddition; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentDefaultValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentRename; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentTypeModification; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldRename; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldTypeModification; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectInterfaceImplementationAddition; +import static graphql.schema.diffing.ana.SchemaDifference.ObjectInterfaceImplementationDeletion; import static graphql.schema.diffing.ana.SchemaDifference.ObjectModification; +import static graphql.schema.diffing.ana.SchemaDifference.ScalarAddition; import static graphql.schema.diffing.ana.SchemaDifference.ScalarDeletion; import static graphql.schema.diffing.ana.SchemaDifference.ScalarDifference; +import static graphql.schema.diffing.ana.SchemaDifference.ScalarModification; +import static graphql.schema.diffing.ana.SchemaDifference.UnionAddition; import static graphql.schema.diffing.ana.SchemaDifference.UnionDeletion; import static graphql.schema.diffing.ana.SchemaDifference.UnionDifference; +import static graphql.schema.diffing.ana.SchemaDifference.UnionMemberAddition; +import static graphql.schema.diffing.ana.SchemaDifference.UnionMemberDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.UnionModification; /** * Higher level GraphQL semantic assigned to @@ -108,10 +156,44 @@ private void handleAppliedDirectives(List editOperations, Mapping for (EditOperation editOperation : editOperations) { switch (editOperation.getOperation()) { case INSERT_VERTEX: - if(editOperation.getTargetVertex().isOfType(SchemaGraph.APPLIED_DIRECTIVE)) { + if (editOperation.getTargetVertex().isOfType(SchemaGraph.APPLIED_DIRECTIVE)) { appliedDirectiveAdded(editOperation); } break; + case CHANGE_VERTEX: + if (editOperation.getTargetVertex().isOfType(SchemaGraph.APPLIED_ARGUMENT)) { + appliedDirectiveArgumentChanged(editOperation); + } + break; + + } + } + + } + + private void appliedDirectiveArgumentChanged(EditOperation editOperation) { + Vertex appliedArgument = editOperation.getTargetVertex(); + String oldArgumentName = editOperation.getSourceVertex().getName(); + String newArgumentName = editOperation.getTargetVertex().getName(); + boolean nameChanged = !oldArgumentName.equals(newArgumentName); + + String oldValue = editOperation.getSourceVertex().get("value"); + String newValue = editOperation.getTargetVertex().get("value"); + boolean valueChanged = !oldValue.equals(newValue); + + + Vertex appliedDirective = newSchemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument); + Vertex container = newSchemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + if (container.isOfType(SchemaGraph.FIELD)) { + Vertex field = container; + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); + if (valueChanged) { + AppliedDirectiveArgumentValueModification argumentValueModification = new AppliedDirectiveArgumentValueModification(location, newArgumentName, oldValue, newValue); + getObjectModification(object.getName()).getDetails().add(argumentValueModification); + } } } @@ -125,7 +207,7 @@ private void appliedDirectiveAdded(EditOperation editOperation) { Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { Vertex object = interfaceOrObjective; - AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(),field.getName()); + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 72555a77b2..c00f8d00cf 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * Any kind of difference between two schemas is a SchemaDifference. @@ -1065,23 +1064,24 @@ interface AppliedDirectiveDifference { } /** - * SCHEMA, - * SCALAR, - * OBJECT, - * FIELD_DEFINITION, - * ARGUMENT_DEFINITION, - * INTERFACE, - * UNION, - * ENUM, - * ENUM_VALUE, - * INPUT_OBJECT, - * INPUT_FIELD_DEFINITION + * SCHEMA, + * SCALAR, + * OBJECT, + * FIELD_DEFINITION, + * ARGUMENT_DEFINITION, + * INTERFACE, + * UNION, + * ENUM, + * ENUM_VALUE, + * INPUT_OBJECT, + * INPUT_FIELD_DEFINITION */ interface AppliedDirectiveLocationDetail { } - class AppliedDirectiveObjectFieldLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveObjectFieldLocation implements AppliedDirectiveLocationDetail { private final String objectName; private final String fieldName; @@ -1098,6 +1098,7 @@ public String getObjectName() { return objectName; } } + class AppliedDirectiveInterfaceFieldLocation implements AppliedDirectiveLocationDetail { private final String interfaceName; private final String fieldName; @@ -1108,39 +1109,48 @@ public AppliedDirectiveInterfaceFieldLocation(String interfaceName, String field } } - class AppliedDirectiveScalarLocation implements AppliedDirectiveLocationDetail{ + class AppliedDirectiveScalarLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveSchemaLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveSchemaLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveObjectLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveObjectLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveInterfaceLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveInterfaceLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveEnumLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveEnumLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveInputObjectLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveInputObjectLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocationDetail{ + + class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocationDetail { } class AppliedDirectiveAddition implements ObjectModificationDetail { - private final AppliedDirectiveLocationDetail locationDetail; + private final AppliedDirectiveLocationDetail locationDetail; private final String name; public AppliedDirectiveAddition(AppliedDirectiveLocationDetail locationDetail, String name) { @@ -1173,8 +1183,34 @@ class AppliedDirectiveArgumentDeletion { } - class AppliedDirectiveArgumentValueModification { + class AppliedDirectiveArgumentValueModification implements ObjectModificationDetail { + private final AppliedDirectiveLocationDetail locationDetail; + private final String argumentName; + private final String oldValue; + private final String newValue; + public AppliedDirectiveArgumentValueModification(AppliedDirectiveLocationDetail locationDetail, String argumentName, String oldValue, String newValue) { + this.locationDetail = locationDetail; + this.argumentName = argumentName; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public AppliedDirectiveLocationDetail getLocationDetail() { + return locationDetail; + } + + public String getArgumentName() { + return argumentName; + } + + public String getOldValue() { + return oldValue; + } + + public String getNewValue() { + return newValue; + } } class AppliedDirectiveArgumentNameModification { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a0350624d8..80b1625589 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -4,7 +4,59 @@ import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification -import static graphql.schema.diffing.ana.SchemaDifference.* +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveAddition +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentAddition +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentDefaultValueModification +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentDeletion +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentRename +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentTypeModification +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveDeletion +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveModification +import static graphql.schema.diffing.ana.SchemaDifference.EnumAddition +import static graphql.schema.diffing.ana.SchemaDifference.EnumDeletion +import static graphql.schema.diffing.ana.SchemaDifference.EnumModification +import static graphql.schema.diffing.ana.SchemaDifference.EnumValueAddition +import static graphql.schema.diffing.ana.SchemaDifference.EnumValueDeletion +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectAddition +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDeletion +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectModification +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceAddition +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldAddition +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentAddition +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentDefaultValueModification +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentDeletion +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentRename +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldArgumentTypeModification +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldDeletion +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldRename +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceFieldTypeModification +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceInterfaceImplementationDeletion +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceModification +import static graphql.schema.diffing.ana.SchemaDifference.ObjectAddition +import static graphql.schema.diffing.ana.SchemaDifference.ObjectDeletion +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldAddition +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentAddition +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentDefaultValueModification +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentDeletion +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentRename +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldArgumentTypeModification +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldDeletion +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldRename +import static graphql.schema.diffing.ana.SchemaDifference.ObjectFieldTypeModification +import static graphql.schema.diffing.ana.SchemaDifference.ObjectInterfaceImplementationAddition +import static graphql.schema.diffing.ana.SchemaDifference.ObjectInterfaceImplementationDeletion +import static graphql.schema.diffing.ana.SchemaDifference.ObjectModification +import static graphql.schema.diffing.ana.SchemaDifference.ScalarAddition +import static graphql.schema.diffing.ana.SchemaDifference.ScalarDeletion +import static graphql.schema.diffing.ana.SchemaDifference.ScalarModification +import static graphql.schema.diffing.ana.SchemaDifference.UnionAddition +import static graphql.schema.diffing.ana.SchemaDifference.UnionDeletion +import static graphql.schema.diffing.ana.SchemaDifference.UnionMemberAddition +import static graphql.schema.diffing.ana.SchemaDifference.UnionMemberDeletion +import static graphql.schema.diffing.ana.SchemaDifference.UnionModification class EditOperationAnalyzerTest extends Specification { @@ -1433,7 +1485,7 @@ class EditOperationAnalyzerTest extends Specification { argTypeModification[0].newType == '[String]!' } - def "object added applied directive"() { + def "object field added applied directive"() { given: def oldSdl = ''' directive @d(arg:String) on FIELD_DEFINITION @@ -1459,6 +1511,34 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "object field applied directive argument value changd"() { + given: + def oldSdl = ''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo1") + } + ''' + def newSdl = ''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo2") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentValueModifications = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentValueModification) + (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" + (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" + argumentValueModifications[0].argumentName == "arg" + argumentValueModifications[0].oldValue == '"foo1"' + argumentValueModifications[0].newValue == '"foo2"' + } + EditOperationAnalysisResult calcDiff( String oldSdl, From 17a8d561270a6efe4691018b79756b24513ca931 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 30 Oct 2022 08:38:16 +1000 Subject: [PATCH 224/294] fix test --- .../groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 06360c906f..a99481af15 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -5,10 +5,8 @@ import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchemaElement import graphql.schema.GraphQLTypeVisitorStub import graphql.schema.SchemaTransformer -import graphql.schema.diffing.ana.SchemaDifference import graphql.util.TraversalControl import graphql.util.TraverserContext -import org.junit.Ignore import spock.lang.Specification import static graphql.TestUtil.schema @@ -28,7 +26,7 @@ class SchemaDiffingTest extends Specification { def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: - schemaGraph.size() == 93 + schemaGraph.size() == 94 } From 426f4abc2c0668e63affe09f2c59d38e32b50bf2 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 30 Oct 2022 08:46:59 +1000 Subject: [PATCH 225/294] higher level schema diffing work --- .../diffing/ana/EditOperationAnalyzer.java | 6 ++++ .../schema/diffing/ana/SchemaDifference.java | 22 +++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 30 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 1b985a198f..01c6f89060 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -8,6 +8,7 @@ import graphql.schema.diffing.Mapping; import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; +import graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentRename; import graphql.schema.idl.ScalarInfo; import java.util.LinkedHashMap; @@ -194,6 +195,11 @@ private void appliedDirectiveArgumentChanged(EditOperation editOperation) { AppliedDirectiveArgumentValueModification argumentValueModification = new AppliedDirectiveArgumentValueModification(location, newArgumentName, oldValue, newValue); getObjectModification(object.getName()).getDetails().add(argumentValueModification); } + if (nameChanged) { + AppliedDirectiveArgumentRename argumentRename = new AppliedDirectiveArgumentRename(location, oldArgumentName, newArgumentName); + getObjectModification(object.getName()).getDetails().add(argumentRename); + + } } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index c00f8d00cf..4159f61e43 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1213,8 +1213,28 @@ public String getNewValue() { } } - class AppliedDirectiveArgumentNameModification { + class AppliedDirectiveArgumentRename implements ObjectModificationDetail { + private final AppliedDirectiveLocationDetail locationDetail; + private final String oldName; + private final String newName; + + public AppliedDirectiveArgumentRename(AppliedDirectiveLocationDetail locationDetail, String oldName, String newName) { + this.locationDetail = locationDetail; + this.oldName = oldName; + this.newName = newName; + } + public AppliedDirectiveLocationDetail getLocationDetail() { + return locationDetail; + } + + public String getOldName() { + return oldName; + } + + public String getNewName() { + return newName; + } } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 80b1625589..a568426cb4 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1511,7 +1511,7 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } - def "object field applied directive argument value changd"() { + def "object field applied directive argument value changed"() { given: def oldSdl = ''' directive @d(arg:String) on FIELD_DEFINITION @@ -1539,6 +1539,34 @@ class EditOperationAnalyzerTest extends Specification { argumentValueModifications[0].newValue == '"foo2"' } + def "object field applied directive argument name changed"() { + given: + def oldSdl = ''' + directive @d(arg1:String, arg2: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1: String, arg2: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg2: "foo") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentRenames = (changes.objectDifferences["Query"] as ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentRename) + def location = argumentRenames[0].locationDetail as AppliedDirectiveObjectFieldLocation + location.objectName == "Query" + location.fieldName == "foo" + argumentRenames[0].oldName == "arg1" + argumentRenames[0].newName == "arg2" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From 2de42f41323d69c9dad3fcaf0c26d48d5c8cdf3c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 31 Oct 2022 09:53:33 +1100 Subject: [PATCH 226/294] Update version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 37dcab18a7..609d59347a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'antlr' id 'signing' id "com.github.johnrengelman.shadow" version "7.1.2" - id "biz.aQute.bnd.builder" version "6.1.0" + id "biz.aQute.bnd.builder" version "6.3.1" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" id "groovy" id "me.champeau.jmh" version "0.6.6" From 322ba6b9e4c109c4127adf3c25f6c4919a0ec16c Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 31 Oct 2022 11:38:00 +1100 Subject: [PATCH 227/294] Avoid an allocation of a chained context in the most common case (#2979) * Avoid an allocation of a chained context in the most common case * Avoid calling back to methods that create state parameters --- src/main/java/graphql/GraphQL.java | 5 +- .../MaxQueryComplexityInstrumentation.java | 9 +- .../MaxQueryDepthInstrumentation.java | 4 +- .../ChainedInstrumentation.java | 99 ++++++-- .../instrumentation/Instrumentation.java | 8 +- .../instrumentation/InstrumentationState.java | 13 ++ .../SimpleInstrumentation.java | 17 +- .../SimplePerformantInstrumentation.java | 218 ++++++++++++++++++ .../DataLoaderDispatcherInstrumentation.java | 40 ++-- .../FieldLevelTrackingApproach.java | 24 +- .../FieldValidationInstrumentation.java | 4 +- .../threadpools/ExecutorInstrumentation.java | 4 +- .../tracing/TracingInstrumentation.java | 19 +- src/test/groovy/graphql/GraphQLTest.groovy | 14 +- .../MaxQueryDepthInstrumentationTest.groovy | 2 +- .../AsyncExecutionStrategyTest.groovy | 13 +- .../AsyncSerialExecutionStrategyTest.groovy | 6 +- ...egyExceptionHandlingEquivalenceTest.groovy | 4 +- .../execution/ExecutionStrategyTest.groovy | 12 +- .../graphql/execution/ExecutionTest.groovy | 47 ++-- .../ChainedInstrumentationStateTest.groovy | 27 +++ .../InstrumentationTest.groovy | 4 +- ...LoaderDispatcherInstrumentationTest.groovy | 9 +- .../FieldValidationTest.groovy | 46 ++-- .../PreparsedDocumentProviderTest.groovy | 4 +- .../readme/InstrumentationExamples.java | 12 +- .../benchmark/IntrospectionBenchmark.java | 8 +- 27 files changed, 496 insertions(+), 176 deletions(-) create mode 100644 src/main/java/graphql/execution/instrumentation/SimplePerformantInstrumentation.java diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 1210ee7115..435ed82935 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -17,7 +17,7 @@ import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.NoContextChainedInstrumentation; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; @@ -35,7 +35,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -575,7 +574,7 @@ private CompletableFuture execute(ExecutionInput executionInput private static Instrumentation checkInstrumentationDefaultState(Instrumentation instrumentation, boolean doNotAddDefaultInstrumentations) { if (doNotAddDefaultInstrumentations) { - return instrumentation == null ? SimpleInstrumentation.INSTANCE : instrumentation; + return instrumentation == null ? SimplePerformantInstrumentation.INSTANCE : instrumentation; } if (instrumentation instanceof DataLoaderDispatcherInstrumentation) { return instrumentation; diff --git a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java index bbe2b85bf0..4b055d4fd4 100644 --- a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java @@ -6,7 +6,7 @@ import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; @@ -22,6 +22,7 @@ import java.util.function.Function; import static graphql.Assert.assertNotNull; +import static graphql.execution.instrumentation.InstrumentationState.ofState; import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; import static java.util.Optional.ofNullable; @@ -32,7 +33,7 @@ * is exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @PublicApi -public class MaxQueryComplexityInstrumentation extends SimpleInstrumentation { +public class MaxQueryComplexityInstrumentation extends SimplePerformantInstrumentation { private static final Logger log = LoggerFactory.getLogger(MaxQueryComplexityInstrumentation.class); @@ -91,7 +92,7 @@ public InstrumentationState createState(InstrumentationCreateStateParameters par @Override public @Nullable InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { - State state = (State) rawState; + State state = ofState(rawState); // for API backwards compatibility reasons we capture the validation parameters, so we can put them into QueryComplexityInfo state.instrumentationValidationParameters.set(parameters); return noOp(); @@ -99,7 +100,7 @@ public InstrumentationState createState(InstrumentationCreateStateParameters par @Override public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters, InstrumentationState rawState) { - State state = (State) rawState; + State state = ofState(rawState); QueryTraverser queryTraverser = newQueryTraverser(instrumentationExecuteOperationParameters.getExecutionContext()); Map valuesByParent = new LinkedHashMap<>(); diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index 20a6e92990..f1cee88228 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -6,7 +6,7 @@ import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -23,7 +23,7 @@ * exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @PublicApi -public class MaxQueryDepthInstrumentation extends SimpleInstrumentation { +public class MaxQueryDepthInstrumentation extends SimplePerformantInstrumentation { private static final Logger log = LoggerFactory.getLogger(MaxQueryDepthInstrumentation.class); diff --git a/src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java b/src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java index 81c5184720..f8fdef568d 100644 --- a/src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; import static graphql.Assert.assertNotNull; import static graphql.collect.ImmutableKit.mapAndDropNulls; @@ -39,6 +40,7 @@ * * @see graphql.execution.instrumentation.Instrumentation */ +@SuppressWarnings("deprecation") @PublicApi public class ChainedInstrumentation implements Instrumentation { @@ -66,12 +68,26 @@ protected InstrumentationState getSpecificState(Instrumentation instrumentation, return chainedInstrumentationState.getState(instrumentation); } + private InstrumentationContext chainedCtx(Function> mapper) { + // if we have zero or 1 instrumentations (and 1 is the most common), then we can avoid an object allocation + // of the ChainedInstrumentationContext since it won't be needed + if (instrumentations.isEmpty()) { + return SimpleInstrumentationContext.noOp(); + } + if (instrumentations.size() == 1) { + return mapper.apply(instrumentations.get(0)); + } + return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, mapper)); + } + + @Override public InstrumentationState createState(InstrumentationCreateStateParameters parameters) { return new ChainedInstrumentationState(instrumentations, parameters); } @Override + @NotNull public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { // these assert methods have been left in so that we truly never call these methods, either in production nor in tests // later when the deprecated methods are removed, this will disappear. @@ -80,131 +96,148 @@ public InstrumentationContext beginExecution(InstrumentationExe @Override public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginExecution(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginParse" + " was called"); } @Override public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginParse(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginValidation" + " was called"); } @Override public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginValidation(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginExecuteOperation" + " was called"); } @Override public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginExecuteOperation(parameters, specificState); - })); - + }); } @Override + @NotNull public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginExecutionStrategy" + " was called"); } @Override public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState state) { - return new ChainedExecutionStrategyInstrumentationContext(mapAndDropNulls(instrumentations, instrumentation -> { + if (instrumentations.isEmpty()) { + return ExecutionStrategyInstrumentationContext.NOOP; + } + Function mapper = instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginExecutionStrategy(parameters, specificState); - })); + }; + if (instrumentations.size() == 1) { + return mapper.apply(instrumentations.get(0)); + } + return new ChainedExecutionStrategyInstrumentationContext(mapAndDropNulls(instrumentations, mapper)); } @Override + @NotNull public InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginSubscribedFieldEvent" + " was called"); } @Override public InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginSubscribedFieldEvent(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext beginField(InstrumentationFieldParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginField" + " was called"); } @Override public InstrumentationContext beginField(InstrumentationFieldParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginField(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginFieldFetch" + " was called"); } @Override public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginFieldFetch(parameters, specificState); - })); + }); } + @Override + @NotNull public InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginFieldComplete" + " was called"); } @Override public InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, instrumentation -> { + return chainedCtx(instrumentation -> { InstrumentationState specificState = getSpecificState(instrumentation, state); return instrumentation.beginFieldComplete(parameters, specificState); - })); + }); } @Override + @NotNull public InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "beginFieldListComplete" + " was called"); } @Override public InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters, InstrumentationState state) { - return new ChainedInstrumentationContext<>(mapAndDropNulls(instrumentations, it -> { - InstrumentationState specificState = getSpecificState(it, state); - return it.beginFieldListComplete(parameters, specificState); - })); + return chainedCtx(instrumentation -> { + InstrumentationState specificState = getSpecificState(instrumentation, state); + return instrumentation.beginFieldListComplete(parameters, specificState); + }); } @Override + @NotNull public ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentExecutionInput" + " was called"); } @@ -212,6 +245,9 @@ public ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, In @NotNull @Override public ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters, InstrumentationState state) { + if (instrumentations.isEmpty()) { + return executionInput; + } for (Instrumentation instrumentation : instrumentations) { InstrumentationState specificState = getSpecificState(instrumentation, state); executionInput = instrumentation.instrumentExecutionInput(executionInput, parameters, specificState); @@ -220,6 +256,7 @@ public ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, In } @Override + @NotNull public DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentDocumentAndVariables" + " was called"); } @@ -227,6 +264,9 @@ public DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables @NotNull @Override public DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters, InstrumentationState state) { + if (instrumentations.isEmpty()) { + return documentAndVariables; + } for (Instrumentation instrumentation : instrumentations) { InstrumentationState specificState = getSpecificState(instrumentation, state); documentAndVariables = instrumentation.instrumentDocumentAndVariables(documentAndVariables, parameters, specificState); @@ -235,6 +275,7 @@ public DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables } @Override + @NotNull public GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentSchema" + " was called"); } @@ -242,6 +283,9 @@ public GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecu @NotNull @Override public GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters, InstrumentationState state) { + if (instrumentations.isEmpty()) { + return schema; + } for (Instrumentation instrumentation : instrumentations) { InstrumentationState specificState = getSpecificState(instrumentation, state); schema = instrumentation.instrumentSchema(schema, parameters, specificState); @@ -250,6 +294,7 @@ public GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecu } @Override + @NotNull public ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentExecutionContext" + " was called"); } @@ -257,6 +302,9 @@ public ExecutionContext instrumentExecutionContext(ExecutionContext executionCon @NotNull @Override public ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters, InstrumentationState state) { + if (instrumentations.isEmpty()) { + return executionContext; + } for (Instrumentation instrumentation : instrumentations) { InstrumentationState specificState = getSpecificState(instrumentation, state); executionContext = instrumentation.instrumentExecutionContext(executionContext, parameters, specificState); @@ -265,6 +313,7 @@ public ExecutionContext instrumentExecutionContext(ExecutionContext executionCon } @Override + @NotNull public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentDataFetcher" + " was called"); } @@ -272,6 +321,9 @@ public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, Instrume @NotNull @Override public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState state) { + if (instrumentations.isEmpty()) { + return dataFetcher; + } for (Instrumentation instrumentation : instrumentations) { InstrumentationState specificState = getSpecificState(instrumentation, state); dataFetcher = instrumentation.instrumentDataFetcher(dataFetcher, parameters, specificState); @@ -280,6 +332,7 @@ public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, Instrume } @Override + @NotNull public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { return Assert.assertShouldNeverHappen("The deprecated " + "instrumentExecutionResult" + " was called"); } diff --git a/src/main/java/graphql/execution/instrumentation/Instrumentation.java b/src/main/java/graphql/execution/instrumentation/Instrumentation.java index 4042e680a2..989364c482 100644 --- a/src/main/java/graphql/execution/instrumentation/Instrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/Instrumentation.java @@ -27,13 +27,13 @@ /** * Provides the capability to instrument the execution steps of a GraphQL query. - * + *

* For example you might want to track which fields are taking the most time to fetch from the backing database * or log what fields are being asked for. - * + *

* Remember that graphql calls can cross threads so make sure you think about the thread safety of any instrumentation * code when you are writing it. - * + *

* Each step gives back an {@link graphql.execution.instrumentation.InstrumentationContext} object. This has two callbacks on it, * one for the step is `dispatched` and one for when the step has `completed`. This is done because many of the "steps" are asynchronous * operations such as fetching data and resolving it into objects. @@ -63,7 +63,7 @@ default InstrumentationState createState() { * * @return a state object that is passed to each method */ - @Nullable + @Nullable default InstrumentationState createState(InstrumentationCreateStateParameters parameters) { return createState(); } diff --git a/src/main/java/graphql/execution/instrumentation/InstrumentationState.java b/src/main/java/graphql/execution/instrumentation/InstrumentationState.java index 2bd9c21573..afea0f4afb 100644 --- a/src/main/java/graphql/execution/instrumentation/InstrumentationState.java +++ b/src/main/java/graphql/execution/instrumentation/InstrumentationState.java @@ -10,4 +10,17 @@ */ @PublicSpi public interface InstrumentationState { + + /** + * This helper method allows you to cast from {@link InstrumentationState} to a custom classes more easily. + * + * @param rawState the raw InstrumentationState + * @param for two + * + * @return a cast custom InstrumentationState + */ + static T ofState(InstrumentationState rawState) { + //noinspection unchecked + return (T) rawState; + } } diff --git a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java index 9925368fa1..a8b27a8718 100644 --- a/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/SimpleInstrumentation.java @@ -1,26 +1,19 @@ package graphql.execution.instrumentation; -import graphql.ExecutionResult; +import graphql.DeprecatedAt; import graphql.PublicApi; -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; -import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; -import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; -import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; -import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters; -import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; -import graphql.language.Document; -import graphql.validation.ValidationError; - -import java.util.List; -import java.util.concurrent.CompletableFuture; /** * An implementation of {@link graphql.execution.instrumentation.Instrumentation} that does nothing. It can be used * as a base for derived classes where you only implement the methods you want to. With all the methods in {@link Instrumentation} * now defaulted (post Java 6) this class is really not needed anymore but has been retained for backwards compatibility * reasons. + * + * @deprecated use {@link SimplePerformantInstrumentation} instead as a base class. */ @PublicApi +@Deprecated +@DeprecatedAt(value = "2022-10-05") public class SimpleInstrumentation implements Instrumentation { /** diff --git a/src/main/java/graphql/execution/instrumentation/SimplePerformantInstrumentation.java b/src/main/java/graphql/execution/instrumentation/SimplePerformantInstrumentation.java new file mode 100644 index 0000000000..b7835ffddd --- /dev/null +++ b/src/main/java/graphql/execution/instrumentation/SimplePerformantInstrumentation.java @@ -0,0 +1,218 @@ +package graphql.execution.instrumentation; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.PublicApi; +import graphql.execution.ExecutionContext; +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters; +import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; +import graphql.language.Document; +import graphql.schema.DataFetcher; +import graphql.schema.GraphQLSchema; +import graphql.validation.ValidationError; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static graphql.Assert.assertShouldNeverHappen; +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; + +/** + * An implementation of {@link Instrumentation} that does nothing. It can be used + * as a base for derived classes where you only implement the methods you want to. The reason this + * class is designated as more performant is that it does not delegate back to the deprecated methods + * and allocate a new state object per call. + *

+ * This behavior was left in place for backwards compatibility reasons inside {@link Instrumentation} + * and {@link SimpleInstrumentation} but has not been done in this class since no existing classes + * could have derived from it. If you want more performant behavior on methods you don't implement + * then this is the base class to use, since it will not delegate back to old methods + * and cause a new state to be allocated. + */ +@SuppressWarnings("deprecation") +@PublicApi +public class SimplePerformantInstrumentation implements Instrumentation { + + /** + * A singleton instance of a {@link Instrumentation} that does nothing + */ + public static final SimplePerformantInstrumentation INSTANCE = new SimplePerformantInstrumentation(); + + @Override + public InstrumentationState createState() { + return assertShouldNeverHappen("The deprecated " + "createState" + " was called"); + } + + @Override + public @Nullable InstrumentationState createState(InstrumentationCreateStateParameters parameters) { + return null; + } + + @Override + public @NotNull InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginExecution" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginParse(InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginParse" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginParse(InstrumentationExecutionParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginValidation" + " was called"); + } + + @Override + public @Nullable InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginExecuteOperation" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginExecutionStrategy" + " was called"); + } + + @Override + public @Nullable ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState state) { + return ExecutionStrategyInstrumentationContext.NOOP; + } + + @Override + public @NotNull InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginSubscribedFieldEvent" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginSubscribedFieldEvent(InstrumentationFieldParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginField(InstrumentationFieldParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginField" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginField(InstrumentationFieldParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginFieldFetch" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginFieldComplete" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginFieldComplete(InstrumentationFieldCompleteParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "beginFieldListComplete" + " was called"); + } + + @Override + public @Nullable InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters, InstrumentationState state) { + return noOp(); + } + + @Override + public @NotNull ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentExecutionInput" + " was called"); + } + + @Override + public @NotNull ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return executionInput; + } + + @Override + public @NotNull DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentDocumentAndVariables" + " was called"); + } + + @Override + public @NotNull DocumentAndVariables instrumentDocumentAndVariables(DocumentAndVariables documentAndVariables, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return documentAndVariables; + } + + @Override + public @NotNull GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentSchema" + " was called"); + } + + @Override + public @NotNull GraphQLSchema instrumentSchema(GraphQLSchema schema, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return schema; + } + + @Override + public @NotNull ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentExecutionContext" + " was called"); + } + + @Override + public @NotNull ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return executionContext; + } + + @Override + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentDataFetcher" + " was called"); + } + + @Override + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState state) { + return dataFetcher; + } + + @Override + public @NotNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { + return assertShouldNeverHappen("The deprecated " + "instrumentExecutionResult" + " was called"); + } + + @Override + public @NotNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState state) { + return CompletableFuture.completedFuture(executionResult); + } +} diff --git a/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java b/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java index 1822fb4cc4..fd328c3ff5 100644 --- a/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java @@ -10,8 +10,7 @@ import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; -import graphql.execution.instrumentation.SimpleInstrumentationContext; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; @@ -22,6 +21,8 @@ import org.dataloader.DataLoader; import org.dataloader.DataLoaderRegistry; import org.dataloader.stats.Statistics; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,9 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; +import static graphql.execution.instrumentation.InstrumentationState.ofState; +import static graphql.execution.instrumentation.SimpleInstrumentationContext.noOp; + /** * This graphql {@link graphql.execution.instrumentation.Instrumentation} will dispatch * all the contained {@link org.dataloader.DataLoader}s when each level of the graphql @@ -44,7 +48,7 @@ * @see org.dataloader.DataLoaderRegistry */ @PublicApi -public class DataLoaderDispatcherInstrumentation extends SimpleInstrumentation { +public class DataLoaderDispatcherInstrumentation extends SimplePerformantInstrumentation { private static final Logger log = LoggerFactory.getLogger(DataLoaderDispatcherInstrumentation.class); @@ -73,14 +77,14 @@ public InstrumentationState createState(InstrumentationCreateStateParameters par } @Override - public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState rawState) { + DataLoaderDispatcherInstrumentationState state = ofState(rawState); if (state.isAggressivelyBatching()) { return dataFetcher; } // // currently only AsyncExecutionStrategy with DataLoader and hence this allows us to "dispatch" - // on every object if its not using aggressive batching for other execution strategies + // on every object if it's not using aggressive batching for other execution strategies // which allows them to work if used. return (DataFetcher) environment -> { Object obj = dataFetcher.get(environment); @@ -94,8 +98,8 @@ private void immediatelyDispatch(DataLoaderDispatcherInstrumentationState state) } @Override - public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters) { - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); + public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState rawState) { + DataLoaderDispatcherInstrumentationState state = ofState(rawState); // // during #instrumentExecutionInput they could have enhanced the data loader registry // so we grab it now just before the query operation gets started @@ -105,7 +109,7 @@ public InstrumentationContext beginExecuteOperation(Instrumenta if (!isDataLoaderCompatibleExecution(parameters.getExecutionContext())) { state.setAggressivelyBatching(false); } - return new SimpleInstrumentationContext<>(); + return noOp(); } private boolean isDataLoaderCompatibleExecution(ExecutionContext executionContext) { @@ -119,8 +123,8 @@ private boolean isDataLoaderCompatibleExecution(ExecutionContext executionContex } @Override - public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); + public @Nullable ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState rawState) { + DataLoaderDispatcherInstrumentationState state = ofState(rawState); // // if there are no data loaders, there is nothing to do // @@ -136,28 +140,28 @@ public void onCompleted(ExecutionResult result, Throwable t) { }; } - return state.getApproach().beginExecutionStrategy(parameters.withNewState(state.getState())); + return state.getApproach().beginExecutionStrategy(parameters, state.getState()); } @Override - public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); + public @Nullable InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState rawState) { + DataLoaderDispatcherInstrumentationState state = ofState(rawState); // // if there are no data loaders, there is nothing to do // if (state.hasNoDataLoaders()) { - return new SimpleInstrumentationContext<>(); + return noOp(); } - return state.getApproach().beginFieldFetch(parameters.withNewState(state.getState())); + return state.getApproach().beginFieldFetch(parameters, state.getState()); } @Override - public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { + public @NotNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { if (!options.isIncludeStatistics()) { return CompletableFuture.completedFuture(executionResult); } - DataLoaderDispatcherInstrumentationState state = parameters.getInstrumentationState(); + DataLoaderDispatcherInstrumentationState state = ofState(rawState); Map currentExt = executionResult.getExtensions(); Map statsMap = new LinkedHashMap<>(currentExt == null ? ImmutableKit.emptyMap() : currentExt); Map dataLoaderStats = buildStatsMap(state); diff --git a/src/main/java/graphql/execution/instrumentation/dataloader/FieldLevelTrackingApproach.java b/src/main/java/graphql/execution/instrumentation/dataloader/FieldLevelTrackingApproach.java index 49bb11ed67..e6e9a5330a 100644 --- a/src/main/java/graphql/execution/instrumentation/dataloader/FieldLevelTrackingApproach.java +++ b/src/main/java/graphql/execution/instrumentation/dataloader/FieldLevelTrackingApproach.java @@ -76,13 +76,13 @@ boolean allFetchesHappened(int level) { @Override public String toString() { return "CallStack{" + - "expectedFetchCountPerLevel=" + expectedFetchCountPerLevel + - ", fetchCountPerLevel=" + fetchCountPerLevel + - ", expectedStrategyCallsPerLevel=" + expectedStrategyCallsPerLevel + - ", happenedStrategyCallsPerLevel=" + happenedStrategyCallsPerLevel + - ", happenedOnFieldValueCallsPerLevel=" + happenedOnFieldValueCallsPerLevel + - ", dispatchedLevels" + dispatchedLevels + - '}'; + "expectedFetchCountPerLevel=" + expectedFetchCountPerLevel + + ", fetchCountPerLevel=" + fetchCountPerLevel + + ", expectedStrategyCallsPerLevel=" + expectedStrategyCallsPerLevel + + ", happenedStrategyCallsPerLevel=" + happenedStrategyCallsPerLevel + + ", happenedOnFieldValueCallsPerLevel=" + happenedOnFieldValueCallsPerLevel + + ", dispatchedLevels" + dispatchedLevels + + '}'; } public boolean dispatchIfNotDispatchedBefore(int level) { @@ -118,8 +118,8 @@ public InstrumentationState createState() { return new CallStack(); } - ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { - CallStack callStack = parameters.getInstrumentationState(); + ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState rawState) { + CallStack callStack = (CallStack) rawState; ResultPath path = parameters.getExecutionStrategyParameters().getPath(); int parentLevel = path.getLevel(); int curLevel = parentLevel + 1; @@ -183,8 +183,8 @@ private int getCountForList(List fieldValueInfos) { } - public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - CallStack callStack = parameters.getInstrumentationState(); + public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState rawState) { + CallStack callStack = (CallStack) rawState; ResultPath path = parameters.getEnvironment().getExecutionStepInfo().getPath(); int level = path.getLevel(); return new InstrumentationContext() { @@ -228,7 +228,7 @@ private boolean levelReady(CallStack callStack, int level) { return callStack.allFetchesHappened(1); } if (levelReady(callStack, level - 1) && callStack.allOnFieldCallsHappened(level - 1) - && callStack.allStrategyCallsHappened(level) && callStack.allFetchesHappened(level)) { + && callStack.allStrategyCallsHappened(level) && callStack.allFetchesHappened(level)) { return true; } return false; diff --git a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java index 2530648124..7dc5d6ba72 100644 --- a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java @@ -6,7 +6,7 @@ import graphql.execution.AbortExecutionException; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import org.jetbrains.annotations.Nullable; @@ -24,7 +24,7 @@ * @see FieldValidation */ @PublicApi -public class FieldValidationInstrumentation extends SimpleInstrumentation { +public class FieldValidationInstrumentation extends SimplePerformantInstrumentation { private final FieldValidation fieldValidation; diff --git a/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java b/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java index bc1de2921b..af210df041 100644 --- a/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/threadpools/ExecutorInstrumentation.java @@ -6,7 +6,7 @@ import graphql.TrivialDataFetcher; import graphql.execution.Async; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -41,7 +41,7 @@ */ @Internal @Beta -public class ExecutorInstrumentation extends SimpleInstrumentation { +public class ExecutorInstrumentation extends SimplePerformantInstrumentation { private static final Consumer NOOP = a -> { }; diff --git a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java index eb6bd87d53..9750ec1475 100644 --- a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java @@ -7,21 +7,22 @@ import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.language.Document; import graphql.validation.ValidationError; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import static graphql.execution.instrumentation.InstrumentationState.ofState; import static graphql.execution.instrumentation.SimpleInstrumentationContext.whenCompleted; /** @@ -29,7 +30,7 @@ * capture tracing information and puts it into the {@link ExecutionResult} */ @PublicApi -public class TracingInstrumentation extends SimpleInstrumentation { +public class TracingInstrumentation extends SimplePerformantInstrumentation { public static class Options { private final boolean includeTrivialDataFetchers; @@ -76,10 +77,10 @@ public TracingInstrumentation(Options options) { } @Override - public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { + public @NotNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { Map currentExt = executionResult.getExtensions(); - TracingSupport tracingSupport = (TracingSupport) rawState; + TracingSupport tracingSupport = ofState(rawState); Map withTracingExt = new LinkedHashMap<>(currentExt == null ? ImmutableKit.emptyMap() : currentExt); withTracingExt.put("tracing", tracingSupport.snapshotTracingData()); @@ -88,21 +89,21 @@ public CompletableFuture instrumentExecutionResult(ExecutionRes @Override public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState rawState) { - TracingSupport tracingSupport = (TracingSupport) rawState; + TracingSupport tracingSupport = ofState(rawState); TracingSupport.TracingContext ctx = tracingSupport.beginField(parameters.getEnvironment(), parameters.isTrivialDataFetcher()); return whenCompleted((result, t) -> ctx.onEnd()); } @Override - public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters,InstrumentationState rawState) { - TracingSupport tracingSupport = (TracingSupport) rawState; + public InstrumentationContext beginParse(InstrumentationExecutionParameters parameters, InstrumentationState rawState) { + TracingSupport tracingSupport = ofState(rawState); TracingSupport.TracingContext ctx = tracingSupport.beginParse(); return whenCompleted((result, t) -> ctx.onEnd()); } @Override public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { - TracingSupport tracingSupport = (TracingSupport) rawState; + TracingSupport tracingSupport = ofState(rawState); TracingSupport.TracingContext ctx = tracingSupport.beginValidation(); return whenCompleted((result, t) -> ctx.onEnd()); } diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index ef2d4e19f2..d6c1b062a1 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -17,7 +17,7 @@ import graphql.execution.SubscriptionExecutionStrategy import graphql.execution.ValueUnboxer import graphql.execution.instrumentation.ChainedInstrumentation import graphql.execution.instrumentation.Instrumentation -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation import graphql.execution.preparsed.NoOpPreparsedDocumentProvider import graphql.language.SourceLocation @@ -883,7 +883,7 @@ class GraphQLTest extends Specification { .build() def codeRegistry = GraphQLCodeRegistry.newCodeRegistry() - .typeResolver(node, {type -> foo }) + .typeResolver(node, { type -> foo }) .build() GraphQLSchema schema = newSchema() @@ -926,7 +926,7 @@ class GraphQLTest extends Specification { def "graphql copying works as expected"() { - def instrumentation = new SimpleInstrumentation() + def instrumentation = new SimplePerformantInstrumentation() def hello = ExecutionId.from("hello") def executionIdProvider = new ExecutionIdProvider() { @Override @@ -956,7 +956,7 @@ class GraphQLTest extends Specification { when: // now make some changes - def newInstrumentation = new SimpleInstrumentation() + def newInstrumentation = new SimplePerformantInstrumentation() def goodbye = ExecutionId.from("goodbye") def newExecutionIdProvider = new ExecutionIdProvider() { @Override @@ -981,7 +981,7 @@ class GraphQLTest extends Specification { def "disabling data loader instrumentation leaves instrumentation as is"() { given: def queryStrategy = new CaptureStrategy() - def instrumentation = new SimpleInstrumentation() + def instrumentation = new SimplePerformantInstrumentation() def builder = GraphQL.newGraphQL(simpleSchema()) .queryExecutionStrategy(queryStrategy) .instrumentation(instrumentation) @@ -1416,7 +1416,7 @@ many lines'''] } def "getters work as expected"() { - Instrumentation instrumentation = new SimpleInstrumentation() + Instrumentation instrumentation = new SimplePerformantInstrumentation() when: def graphQL = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema).instrumentation(instrumentation).build() then: @@ -1438,6 +1438,6 @@ many lines'''] when: def er = graphQL.execute(ei) then: - ! er.errors.isEmpty() + !er.errors.isEmpty() } } diff --git a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy index f33074f217..942bee10dc 100644 --- a/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy +++ b/src/test/groovy/graphql/analysis/MaxQueryDepthInstrumentationTest.groovy @@ -94,7 +94,7 @@ class MaxQueryDepthInstrumentationTest extends Specification { def executionContext = executionCtx(executionInput, query, schema) def executeOperationParameters = new InstrumentationExecuteOperationParameters(executionContext) when: - maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters) // Retain for test coverage + maximumQueryDepthInstrumentation.beginExecuteOperation(executeOperationParameters, null) // Retain for test coverage then: notThrown(Exception) } diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 8d3e76607e..951246df0c 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -5,7 +5,7 @@ import graphql.ExecutionResult import graphql.GraphQLContext import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.language.Field import graphql.language.OperationDefinition @@ -96,7 +96,7 @@ class AsyncExecutionStrategyTest extends Specification { .graphQLSchema(schema) .executionId(ExecutionId.generate()) .operationDefinition(operation) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) .graphQLContext(GraphQLContext.getDefault()) .locale(Locale.getDefault()) @@ -138,7 +138,7 @@ class AsyncExecutionStrategyTest extends Specification { .executionId(ExecutionId.generate()) .operationDefinition(operation) .valueUnboxer(ValueUnboxer.DEFAULT) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) .build() @@ -181,7 +181,7 @@ class AsyncExecutionStrategyTest extends Specification { .executionId(ExecutionId.generate()) .operationDefinition(operation) .valueUnboxer(ValueUnboxer.DEFAULT) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .graphQLContext(GraphQLContext.getDefault()) .locale(Locale.getDefault()) .build() @@ -222,7 +222,7 @@ class AsyncExecutionStrategyTest extends Specification { .graphQLSchema(schema) .executionId(ExecutionId.generate()) .operationDefinition(operation) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) @@ -266,7 +266,8 @@ class AsyncExecutionStrategyTest extends Specification { .valueUnboxer(ValueUnboxer.DEFAULT) .graphQLContext(GraphQLContext.getDefault()) .locale(Locale.getDefault()) - .instrumentation(new SimpleInstrumentation() { + .instrumentation(new SimplePerformantInstrumentation() { + @Override ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters, InstrumentationState state) { return new ExecutionStrategyInstrumentationContext() { diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index 1b844afb56..e35ea56d6d 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -1,7 +1,7 @@ package graphql.execution import graphql.GraphQLContext -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.language.Field import graphql.language.OperationDefinition import graphql.parser.Parser @@ -101,7 +101,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .graphQLSchema(schema) .executionId(ExecutionId.generate()) .operationDefinition(operation) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) @@ -147,7 +147,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .graphQLSchema(schema) .executionId(ExecutionId.generate()) .operationDefinition(operation) - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .valueUnboxer(ValueUnboxer.DEFAULT) .locale(Locale.getDefault()) .graphQLContext(GraphQLContext.getDefault()) diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy index cbbbf34bcd..af179821cb 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy @@ -5,7 +5,7 @@ import graphql.GraphQL import graphql.StarWarsSchema import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import graphql.validation.ValidationError import graphql.validation.ValidationErrorType @@ -14,7 +14,7 @@ import spock.lang.Unroll class ExecutionStrategyExceptionHandlingEquivalenceTest extends Specification { - class TestInstrumentation extends SimpleInstrumentation { + class TestInstrumentation extends SimplePerformantInstrumentation { @Override InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState state) { diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index b4d28e7f2c..63ba108a3e 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -11,7 +11,7 @@ import graphql.StarWarsSchema import graphql.TypeMismatchError import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters import graphql.language.Argument import graphql.language.Field @@ -68,14 +68,14 @@ class ExecutionStrategyTest extends Specification { ExecutionId executionId = ExecutionId.from("executionId123") def variables = [arg1: "value1"] def builder = ExecutionContextBuilder.newExecutionContextBuilder() - .instrumentation(SimpleInstrumentation.INSTANCE) + .instrumentation(SimplePerformantInstrumentation.INSTANCE) .executionId(executionId) .graphQLSchema(schema ?: StarWarsSchema.starWarsSchema) .queryStrategy(executionStrategy) .mutationStrategy(executionStrategy) .subscriptionStrategy(executionStrategy) .coercedVariables(CoercedVariables.of(variables)) - .graphQLContext(GraphQLContext.newContext().of("key","context").build()) + .graphQLContext(GraphQLContext.newContext().of("key", "context").build()) .root("root") .dataLoaderRegistry(new DataLoaderRegistry()) .locale(Locale.getDefault()) @@ -455,7 +455,7 @@ class ExecutionStrategyTest extends Specification { throw new UnsupportedOperationException("Not implemented") } }) - .build() + .build() ExecutionContext executionContext = buildContext() @@ -675,7 +675,7 @@ class ExecutionStrategyTest extends Specification { def (ExecutionContext executionContext, GraphQLFieldDefinition fieldDefinition, ResultPath expectedPath, ExecutionStrategyParameters params, Field field, SourceLocation sourceLocation) = exceptionSetupFixture(expectedException) ExecutionContextBuilder executionContextBuilder = ExecutionContextBuilder.newExecutionContextBuilder(executionContext) - def instrumentation = new SimpleInstrumentation() { + def instrumentation = new SimplePerformantInstrumentation() { Map fetchedValues = [:] @Override @@ -866,7 +866,7 @@ class ExecutionStrategyTest extends Specification { fetchedValue.getFetchedValue() == executionData // executionContext.getErrors()[0].locations == [new SourceLocation(7, 20)] executionContext.getErrors()[0].message == "bad foo" - executionContext.getErrors()[0].path == [ "child", "foo"] + executionContext.getErrors()[0].path == ["child", "foo"] } def "#1558 forward localContext on nonBoxed return from DataFetcher"() { diff --git a/src/test/groovy/graphql/execution/ExecutionTest.groovy b/src/test/groovy/graphql/execution/ExecutionTest.groovy index d6d972a7f6..0cc8f1d287 100644 --- a/src/test/groovy/graphql/execution/ExecutionTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionTest.groovy @@ -5,9 +5,10 @@ import graphql.ExecutionResult import graphql.ExecutionResultImpl import graphql.MutationSchema import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.parser.Parser +import org.jetbrains.annotations.NotNull import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -35,7 +36,7 @@ class ExecutionTest extends Specification { def subscriptionStrategy = new CountingExecutionStrategy() def mutationStrategy = new CountingExecutionStrategy() def queryStrategy = new CountingExecutionStrategy() - def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, SimpleInstrumentation.INSTANCE, ValueUnboxer.DEFAULT) + def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, SimplePerformantInstrumentation.INSTANCE, ValueUnboxer.DEFAULT) def emptyExecutionInput = ExecutionInput.newExecutionInput().query("query").build() def instrumentationState = new InstrumentationState() {} @@ -98,20 +99,20 @@ class ExecutionTest extends Specification { mutationStrategy.execute == 0 subscriptionStrategy.execute == 1 } - - def "Update query strategy when instrumenting execution context" (){ - given: - def query = ''' + + def "Update query strategy when instrumenting execution context"() { + given: + def query = ''' query { numberHolder { theNumber } } ''' - def document = parser.parseDocument(query) - def queryStrategyUpdatedToDuringExecutionContextInstrument = new CountingExecutionStrategy() - - def instrumentation = new SimpleInstrumentation() { + def document = parser.parseDocument(query) + def queryStrategyUpdatedToDuringExecutionContextInstrument = new CountingExecutionStrategy() + + def instrumentation = new SimplePerformantInstrumentation() { @Override ExecutionContext instrumentExecutionContext(ExecutionContext executionContext, @@ -125,17 +126,17 @@ class ExecutionTest extends Specification { } def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, ValueUnboxer.DEFAULT) - - - when: - execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) - - then: - queryStrategy.execute == 0 - mutationStrategy.execute == 0 - subscriptionStrategy.execute == 0 - queryStrategyUpdatedToDuringExecutionContextInstrument.execute == 1 - } - - + + + when: + execution.execute(document, MutationSchema.schema, ExecutionId.generate(), emptyExecutionInput, instrumentationState) + + then: + queryStrategy.execute == 0 + mutationStrategy.execute == 0 + subscriptionStrategy.execute == 0 + queryStrategyUpdatedToDuringExecutionContextInstrument.execute == 1 + } + + } diff --git a/src/test/groovy/graphql/execution/instrumentation/ChainedInstrumentationStateTest.groovy b/src/test/groovy/graphql/execution/instrumentation/ChainedInstrumentationStateTest.groovy index 90c858e9c4..c2c1ffb8ca 100644 --- a/src/test/groovy/graphql/execution/instrumentation/ChainedInstrumentationStateTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/ChainedInstrumentationStateTest.groovy @@ -252,6 +252,33 @@ class ChainedInstrumentationStateTest extends Specification { } + def "single chain"() { + def a = new NamedInstrumentation("A") + def chainedInstrumentation = new ChainedInstrumentation([a]) + + def query = """ + query HeroNameAndFriendsQuery { + hero { + id + } + } + """ + + when: + def graphQL = GraphQL + .newGraphQL(StarWarsSchema.starWarsSchema) + .instrumentation(chainedInstrumentation) + .build() + + graphQL.execute(query) + + then: + noExceptionThrown() + + assertCalls(a) + + } + private void assertCalls(NamedInstrumentation instrumentation) { assert instrumentation.dfInvocations[0].getFieldDefinition().name == 'hero' assert instrumentation.dfInvocations[0].getExecutionStepInfo().getPath().toList() == ['hero'] diff --git a/src/test/groovy/graphql/execution/instrumentation/InstrumentationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/InstrumentationTest.groovy index f5e74eb510..694e100c0d 100644 --- a/src/test/groovy/graphql/execution/instrumentation/InstrumentationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/InstrumentationTest.groovy @@ -15,6 +15,7 @@ import graphql.schema.DataFetchingEnvironment import graphql.schema.PropertyDataFetcher import graphql.schema.StaticDataFetcher import org.awaitility.Awaitility +import org.jetbrains.annotations.NotNull import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -158,7 +159,7 @@ class InstrumentationTest extends Specification { * java-dataloader works. That is calls inside DataFetchers are "batched" * until a "dispatch" signal is made. */ - class WaitingInstrumentation extends SimpleInstrumentation { + class WaitingInstrumentation extends SimplePerformantInstrumentation { final AtomicBoolean goSignal = new AtomicBoolean() @@ -180,6 +181,7 @@ class InstrumentationTest extends Specification { } } + @NotNull @Override DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState state) { System.out.println(String.format("t%s instrument DF for %s", Thread.currentThread().getId(), parameters.environment.getExecutionStepInfo().getPath())) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy index ad553a76c2..ca01f1b3d9 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentationTest.groovy @@ -11,12 +11,13 @@ import graphql.execution.ExecutionStrategyParameters import graphql.execution.instrumentation.ChainedInstrumentation import graphql.execution.instrumentation.Instrumentation import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.schema.DataFetcher import org.dataloader.BatchLoader import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry +import org.jetbrains.annotations.NotNull import spock.lang.Specification import spock.lang.Unroll @@ -68,7 +69,7 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { def captureStrategy = new CaptureStrategy() def graphQL = GraphQL.newGraphQL(starWarsSchema).queryExecutionStrategy(captureStrategy) - .instrumentation(new SimpleInstrumentation()) + .instrumentation(new SimplePerformantInstrumentation()) .build() def executionInput = newExecutionInput().query('{ hero { name } }').build() when: @@ -131,7 +132,9 @@ class DataLoaderDispatcherInstrumentationTest extends Specification { def enhancedDataLoaderRegistry = starWarsWiring.newDataLoaderRegistry() def dlInstrumentation = new DataLoaderDispatcherInstrumentation() - def enhancingInstrumentation = new SimpleInstrumentation() { + def enhancingInstrumentation = new SimplePerformantInstrumentation() { + + @NotNull @Override ExecutionInput instrumentExecutionInput(ExecutionInput executionInput, InstrumentationExecutionParameters parameters, InstrumentationState state) { assert executionInput.getDataLoaderRegistry() == startingDataLoaderRegistry diff --git a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy index 9208d935c3..9d473da226 100644 --- a/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/fieldvalidation/FieldValidationTest.groovy @@ -11,7 +11,7 @@ import graphql.execution.Execution import graphql.execution.ExecutionId import graphql.execution.ResultPath import graphql.execution.ValueUnboxer -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters import spock.lang.Specification @@ -155,11 +155,11 @@ class FieldValidationTest extends Specification { SimpleFieldValidation validation = new SimpleFieldValidation() .addRule(ResultPath.parse("/field1"), - { fieldAndArguments, env -> err("Not happy Jan!", env, fieldAndArguments) }) + { fieldAndArguments, env -> err("Not happy Jan!", env, fieldAndArguments) }) .addRule(ResultPath.parse("/field1/informationLink/informationLink/informationString"), - { fieldAndArguments, env -> err("Also not happy Jan!", env, fieldAndArguments) }) + { fieldAndArguments, env -> err("Also not happy Jan!", env, fieldAndArguments) }) .addRule(ResultPath.parse("/does/not/exist"), - { fieldAndArguments, env -> err("Wont happen", env, fieldAndArguments) }) + { fieldAndArguments, env -> err("Wont happen", env, fieldAndArguments) }) when: @@ -204,15 +204,15 @@ class FieldValidationTest extends Specification { SimpleFieldValidation validation = new SimpleFieldValidation() .addRule(ResultPath.parse("/field1/informationString"), - { fieldAndArguments, env -> - def value = fieldAndArguments.getArgumentValue("fmt1") - if (value != "ok") { - return err("Nope : " + value, env, fieldAndArguments) - } else { - return Optional.empty() - } - } - ) + { fieldAndArguments, env -> + def value = fieldAndArguments.getArgumentValue("fmt1") + if (value != "ok") { + return err("Nope : " + value, env, fieldAndArguments) + } else { + return Optional.empty() + } + } + ) when: @@ -250,15 +250,15 @@ class FieldValidationTest extends Specification { SimpleFieldValidation validation = new SimpleFieldValidation() .addRule(ResultPath.parse("/field1/informationString"), - { fieldAndArguments, env -> - String value = fieldAndArguments.getArgumentValue("fmt1") - if (value.contains("alias")) { - return err("Nope : " + value, env, fieldAndArguments) - } else { - return Optional.empty() - } - } - ) + { fieldAndArguments, env -> + String value = fieldAndArguments.getArgumentValue("fmt1") + if (value.contains("alias")) { + return err("Nope : " + value, env, fieldAndArguments) + } else { + return Optional.empty() + } + } + ) when: @@ -309,7 +309,7 @@ class FieldValidationTest extends Specification { def execution = new Execution(strategy, strategy, strategy, instrumentation, ValueUnboxer.DEFAULT) def executionInput = ExecutionInput.newExecutionInput().query(query).variables(variables).build() - execution.execute(document, schema, ExecutionId.generate(), executionInput, SimpleInstrumentation.INSTANCE.createState(new InstrumentationCreateStateParameters(schema, executionInput))) + execution.execute(document, schema, ExecutionId.generate(), executionInput, SimplePerformantInstrumentation.INSTANCE.createState(new InstrumentationCreateStateParameters(schema, executionInput))) } } diff --git a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy index 6f48274141..104d00ea28 100644 --- a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy +++ b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentProviderTest.groovy @@ -8,7 +8,7 @@ import graphql.execution.AsyncExecutionStrategy import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.LegacyTestingInstrumentation -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.language.Document import graphql.parser.Parser @@ -163,7 +163,7 @@ class PreparsedDocumentProviderTest extends Specification { result1.errors[0].errorType == result2.errors[0].errorType } - class InputCapturingInstrumentation extends SimpleInstrumentation { + class InputCapturingInstrumentation extends SimplePerformantInstrumentation { ExecutionInput capturedInput @Override diff --git a/src/test/groovy/readme/InstrumentationExamples.java b/src/test/groovy/readme/InstrumentationExamples.java index bae144b21e..3fa00e641d 100644 --- a/src/test/groovy/readme/InstrumentationExamples.java +++ b/src/test/groovy/readme/InstrumentationExamples.java @@ -8,8 +8,8 @@ import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.InstrumentationState; -import graphql.execution.instrumentation.SimpleInstrumentation; import graphql.execution.instrumentation.SimpleInstrumentationContext; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.fieldvalidation.FieldAndArguments; import graphql.execution.instrumentation.fieldvalidation.FieldValidation; import graphql.execution.instrumentation.fieldvalidation.FieldValidationEnvironment; @@ -20,6 +20,8 @@ import graphql.execution.instrumentation.tracing.TracingInstrumentation; import graphql.schema.DataFetcher; import graphql.schema.GraphQLSchema; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; @@ -60,7 +62,7 @@ void recordTiming(String key, long time) { } } - class CustomInstrumentation extends SimpleInstrumentation { + class CustomInstrumentation extends SimplePerformantInstrumentation { @Override public InstrumentationState createState() { // @@ -71,7 +73,7 @@ public InstrumentationState createState() { } @Override - public InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters) { + public @Nullable InstrumentationContext beginExecution(InstrumentationExecutionParameters parameters, InstrumentationState state) { long startNanos = System.nanoTime(); return new SimpleInstrumentationContext() { @Override @@ -83,7 +85,7 @@ public void onCompleted(ExecutionResult result, Throwable t) { } @Override - public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { // // this allows you to intercept the data fetcher used to fetch a field and provide another one, perhaps // that enforces certain behaviours or has certain side effects on the data @@ -92,7 +94,7 @@ public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, Instrume } @Override - public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { + public @NotNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { // // this allows you to instrument the execution result some how. For example the Tracing support uses this to put // the `extensions` map of data in place diff --git a/src/test/java/benchmark/IntrospectionBenchmark.java b/src/test/java/benchmark/IntrospectionBenchmark.java index 41dde6bc82..603924c9e8 100644 --- a/src/test/java/benchmark/IntrospectionBenchmark.java +++ b/src/test/java/benchmark/IntrospectionBenchmark.java @@ -5,7 +5,8 @@ import graphql.ExecutionResult; import graphql.GraphQL; import graphql.execution.DataFetcherResult; -import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; import graphql.introspection.IntrospectionQuery; import graphql.schema.DataFetcher; @@ -13,6 +14,7 @@ import graphql.schema.GraphQLNamedType; import graphql.schema.GraphQLSchema; import graphql.schema.idl.SchemaGenerator; +import org.jetbrains.annotations.NotNull; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Measurement; @@ -36,12 +38,12 @@ public class IntrospectionBenchmark { private final GraphQL graphQL; private final DFCountingInstrumentation countingInstrumentation = new DFCountingInstrumentation(); - static class DFCountingInstrumentation extends SimpleInstrumentation { + static class DFCountingInstrumentation extends SimplePerformantInstrumentation { Map counts = new LinkedHashMap<>(); Map times = new LinkedHashMap<>(); @Override - public DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters) { + public @NotNull DataFetcher instrumentDataFetcher(DataFetcher dataFetcher, InstrumentationFieldFetchParameters parameters, InstrumentationState state) { return (DataFetcher) env -> { long then = System.nanoTime(); Object value = dataFetcher.get(env); From c6652489af1fa4fcb970baa5e1da13b40924c01f Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Mon, 31 Oct 2022 02:18:37 +0100 Subject: [PATCH 228/294] Polishing (#3001) * ImmutableKit cleanups - adding @SafeVarargs to ImmutableKit - dropping unused methods * Migrating links to the spec to https://spec.graphql.org/October2021 --- src/main/java/graphql/ExecutionResult.java | 2 +- src/main/java/graphql/GraphQLError.java | 2 +- src/main/java/graphql/Scalars.java | 12 +++---- .../java/graphql/collect/ImmutableKit.java | 31 ++----------------- .../java/graphql/execution/Execution.java | 6 ++-- .../graphql/execution/ExecutionContext.java | 2 +- .../graphql/execution/ExecutionStrategy.java | 4 +-- .../execution/NonNullableFieldValidator.java | 6 ++-- .../NonNullableFieldWasNullException.java | 2 +- .../graphql/schema/GraphQLScalarType.java | 4 +-- .../impl/GraphQLTypeCollectingVisitor.java | 2 +- .../rules/UniqueOperationNames.java | 2 +- .../graphql/NullValueSupportTest.groovy | 2 +- .../graphql/collect/ImmutableKitTest.groovy | 8 ----- .../validation/SpecValidation282Test.groovy | 2 +- .../validation/SpecValidation51Test.groovy | 2 +- .../validation/SpecValidation562Test.groovy | 2 +- .../validation/SpecValidation573Test.groovy | 2 +- .../validation/SpecValidationBase.groovy | 2 +- .../validation/SpecValidationSchema.java | 2 +- .../validation/SpecValidationSchemaPojos.java | 2 +- src/test/groovy/readme/ReadmeExamples.java | 2 +- 22 files changed, 33 insertions(+), 68 deletions(-) diff --git a/src/main/java/graphql/ExecutionResult.java b/src/main/java/graphql/ExecutionResult.java index c05251ecc6..4870144fe5 100644 --- a/src/main/java/graphql/ExecutionResult.java +++ b/src/main/java/graphql/ExecutionResult.java @@ -50,7 +50,7 @@ public interface ExecutionResult { * should be present. Certain JSON serializers may or may interpret {@link ExecutionResult} to spec, so this method * is provided to produce a map that strictly follows the specification. * - * See : http://facebook.github.io/graphql/#sec-Response-Format + * See : https://spec.graphql.org/October2021/#sec-Response-Format * * @return a map of the result that strictly follows the spec */ diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 69873b5911..74534bca64 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -38,7 +38,7 @@ public interface GraphQLError extends Serializable { /** * The graphql spec says that the (optional) path field of any error should be a list - * of path entries - http://facebook.github.io/graphql/#sec-Errors + * of path entries https://spec.graphql.org/October2021/#sec-Handling-Field-Errors * * @return the path in list format */ diff --git a/src/main/java/graphql/Scalars.java b/src/main/java/graphql/Scalars.java index 91754f568d..4a72d248ce 100644 --- a/src/main/java/graphql/Scalars.java +++ b/src/main/java/graphql/Scalars.java @@ -13,13 +13,13 @@ * by the graphql specification (Int, Float, String, Boolean and ID) while others are offer because they are common on * Java platforms. *

- * For more info see http://graphql.org/learn/schema/#scalar-types and more specifically http://facebook.github.io/graphql/#sec-Scalars + * For more info see http://graphql.org/learn/schema/#scalar-types and more specifically https://spec.graphql.org/October2021/#sec-Scalars */ @PublicApi public class Scalars { /** - * This represents the "Int" type as defined in the graphql specification : http://facebook.github.io/graphql/#sec-Int + * This represents the "Int" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Int *

* The Int scalar type represents a signed 32‐bit numeric non‐fractional value. */ @@ -27,7 +27,7 @@ public class Scalars { .name("Int").description("Built-in Int").coercing(new GraphqlIntCoercing()).build(); /** - * This represents the "Float" type as defined in the graphql specification : http://facebook.github.io/graphql/#sec-Float + * This represents the "Float" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Float *

* Note: The Float type in GraphQL is equivalent to Double in Java. (double precision IEEE 754) */ @@ -35,19 +35,19 @@ public class Scalars { .name("Float").description("Built-in Float").coercing(new GraphqlFloatCoercing()).build(); /** - * This represents the "String" type as defined in the graphql specification : http://facebook.github.io/graphql/#sec-String + * This represents the "String" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-String */ public static final GraphQLScalarType GraphQLString = GraphQLScalarType.newScalar() .name("String").description("Built-in String").coercing(new GraphqlStringCoercing()).build(); /** - * This represents the "Boolean" type as defined in the graphql specification : http://facebook.github.io/graphql/#sec-Boolean + * This represents the "Boolean" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Boolean */ public static final GraphQLScalarType GraphQLBoolean = GraphQLScalarType.newScalar() .name("Boolean").description("Built-in Boolean").coercing(new GraphqlBooleanCoercing()).build(); /** - * This represents the "ID" type as defined in the graphql specification : http://facebook.github.io/graphql/#sec-ID + * This represents the "ID" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-ID *

* The ID scalar type represents a unique identifier, often used to re-fetch an object or as the key for a cache. The * ID type is serialized in the same way as a String; however, it is not intended to be human‐readable. While it is diff --git a/src/main/java/graphql/collect/ImmutableKit.java b/src/main/java/graphql/collect/ImmutableKit.java index 6708696958..af74316b69 100644 --- a/src/main/java/graphql/collect/ImmutableKit.java +++ b/src/main/java/graphql/collect/ImmutableKit.java @@ -27,39 +27,10 @@ public static ImmutableMap emptyMap() { return ImmutableMap.of(); } - /** - * ImmutableMaps are hard to build via {@link Map#computeIfAbsent(Object, Function)} style. This method - * allows you to take a mutable map with mutable list of keys and make it immutable. - *

- * This of course has a cost - if the map is very large you will be using more memory. But for static - * maps that live a long life it maybe be worth it. - * - * @param startingMap the starting input map - * @param for key - * @param for victory - * - * @return and Immutable map of ImmutableList values - */ - - public static ImmutableMap> toImmutableMapOfLists(Map> startingMap) { - assertNotNull(startingMap); - ImmutableMap.Builder> map = ImmutableMap.builder(); - for (Map.Entry> e : startingMap.entrySet()) { - ImmutableList value = ImmutableList.copyOf(startingMap.getOrDefault(e.getKey(), emptyList())); - map.put(e.getKey(), value); - } - return map.build(); - } - - public static ImmutableMap addToMap(Map existing, K newKey, V newVal) { return ImmutableMap.builder().putAll(existing).put(newKey, newVal).build(); } - public static ImmutableMap mergeMaps(Map m1, Map m2) { - return ImmutableMap.builder().putAll(m1).putAll(m2).build(); - } - public static ImmutableList concatLists(List l1, List l2) { //noinspection UnstableApiUsage return ImmutableList.builderWithExpectedSize(l1.size() + l2.size()).addAll(l1).addAll(l2).build(); @@ -123,6 +94,7 @@ public static ImmutableList mapAndDropNulls(Iterable iter * * @return an Immutable list with the extra items. */ + @SafeVarargs public static ImmutableList addToList(Collection existing, T newValue, T... extraValues) { assertNotNull(existing); assertNotNull(newValue); @@ -147,6 +119,7 @@ public static ImmutableList addToList(Collection existing, T * * @return an Immutable Set with the extra items. */ + @SafeVarargs public static ImmutableSet addToSet(Collection existing, T newValue, T... extraValues) { assertNotNull(existing); assertNotNull(newValue); diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index f7e97ed774..e09f13ada7 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -155,12 +155,12 @@ private CompletableFuture executeOperation(ExecutionContext exe } result = executionStrategy.execute(executionContext, parameters); } catch (NonNullableFieldWasNullException e) { - // this means it was non null types all the way from an offending non null type - // up to the root object type and there was a a null value some where. + // this means it was non-null types all the way from an offending non-null type + // up to the root object type and there was a null value somewhere. // // The spec says we should return null for the data in this case // - // http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability + // https://spec.graphql.org/October2021/#sec-Handling-Field-Errors // result = completedFuture(new ExecutionResultImpl(null, executionContext.getErrors())); } diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index 26f4b575b7..9c1aa2032b 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -189,7 +189,7 @@ public ValueUnboxer getValueUnboxer() { public void addError(GraphQLError error, ResultPath fieldPath) { synchronized (this) { // - // see http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability about how per + // see https://spec.graphql.org/October2021/#sec-Handling-Field-Errors about how per // field errors should be handled - ie only once per field if it's already there for nullability // but unclear if it's not that error path // diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 2286192afd..81e4756dc0 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -106,7 +106,7 @@ * and the other "completeXXX" methods. *

* The order of fields fetching and completion is up to the execution strategy. As the graphql specification - * http://facebook.github.io/graphql/#sec-Normal-and-Serial-Execution says: + * https://spec.graphql.org/October2021/#sec-Normal-and-Serial-Execution says: *

* Normally the executor can execute the entries in a grouped field set in whatever order it chooses (often in parallel). Because * the resolution of fields other than top-level mutation fields must always be side effect-free and idempotent, the @@ -746,7 +746,7 @@ protected GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObject } /** - * See (...), + * See (...), *

* If a non nullable child field type actually resolves to a null value and the parent type is nullable * then the parent must in fact become null diff --git a/src/main/java/graphql/execution/NonNullableFieldValidator.java b/src/main/java/graphql/execution/NonNullableFieldValidator.java index 73f4f531af..af26b69008 100644 --- a/src/main/java/graphql/execution/NonNullableFieldValidator.java +++ b/src/main/java/graphql/execution/NonNullableFieldValidator.java @@ -4,10 +4,10 @@ import graphql.Internal; /** - * This will check that a value is non null when the type definition says it must be and it will throw {@link NonNullableFieldWasNullException} + * This will check that a value is non-null when the type definition says it must be and, it will throw {@link NonNullableFieldWasNullException} * if this is not the case. * - * See: http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability + * See: https://spec.graphql.org/October2021/#sec-Errors-and-Non-Nullability */ @Internal public class NonNullableFieldValidator { @@ -34,7 +34,7 @@ public NonNullableFieldValidator(ExecutionContext executionContext, ExecutionSte public T validate(ResultPath path, T result) throws NonNullableFieldWasNullException { if (result == null) { if (executionStepInfo.isNonNullType()) { - // see http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability + // see https://spec.graphql.org/October2021/#sec-Errors-and-Non-Nullability // // > If the field returns null because of an error which has already been added to the "errors" list in the response, // > the "errors" list must not be further affected. That is, only one error should be added to the errors list per field. diff --git a/src/main/java/graphql/execution/NonNullableFieldWasNullException.java b/src/main/java/graphql/execution/NonNullableFieldWasNullException.java index d21893ef2d..87324bb005 100644 --- a/src/main/java/graphql/execution/NonNullableFieldWasNullException.java +++ b/src/main/java/graphql/execution/NonNullableFieldWasNullException.java @@ -7,7 +7,7 @@ import static graphql.schema.GraphQLTypeUtil.simplePrint; /** - * See (http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability), but if a non nullable field + * See (https://spec.graphql.org/October2021/#sec-Errors-and-Non-Nullability), but if a non nullable field * actually resolves to a null value and the parent type is nullable then the parent must in fact become null * so we use exceptions to indicate this special case */ diff --git a/src/main/java/graphql/schema/GraphQLScalarType.java b/src/main/java/graphql/schema/GraphQLScalarType.java index b0443281e0..137a6047c0 100644 --- a/src/main/java/graphql/schema/GraphQLScalarType.java +++ b/src/main/java/graphql/schema/GraphQLScalarType.java @@ -28,7 +28,7 @@ * for example, a GraphQL system could define a scalar called Time which, while serialized as a string, promises to * conform to ISO‐8601. When querying a field of type Time, you can then rely on the ability to parse the result with an ISO‐8601 parser and use a client‐specific primitive for time. *

- * From the spec : http://facebook.github.io/graphql/#sec-Scalars + * From the spec : https://spec.graphql.org/October2021/#sec-Scalars *

*

* graphql-java ships with a set of predefined scalar types via {@link graphql.Scalars} @@ -301,4 +301,4 @@ public GraphQLScalarType build() { specifiedByUrl); } } -} \ No newline at end of file +} diff --git a/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java index 7da9ab1f88..79116a6df8 100644 --- a/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java +++ b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java @@ -134,7 +134,7 @@ private void save(String name, GraphQLNamedType type) { /* - From http://facebook.github.io/graphql/#sec-Type-System + From https://spec.graphql.org/October2021/#sec-Type-System All types within a GraphQL schema must have unique names. No two provided types may have the same name. No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types). diff --git a/src/main/java/graphql/validation/rules/UniqueOperationNames.java b/src/main/java/graphql/validation/rules/UniqueOperationNames.java index 3b6d1e08f2..f3eecf00d8 100644 --- a/src/main/java/graphql/validation/rules/UniqueOperationNames.java +++ b/src/main/java/graphql/validation/rules/UniqueOperationNames.java @@ -13,7 +13,7 @@ /** * A GraphQL document is only valid if all defined operations have unique names. - * http://facebook.github.io/graphql/October2016/#sec-Operation-Name-Uniqueness + * https://spec.graphql.org/October2021/#sec-Operation-Name-Uniqueness */ @Internal public class UniqueOperationNames extends AbstractRule { diff --git a/src/test/groovy/graphql/NullValueSupportTest.groovy b/src/test/groovy/graphql/NullValueSupportTest.groovy index 633117438c..0a854cb6c3 100644 --- a/src/test/groovy/graphql/NullValueSupportTest.groovy +++ b/src/test/groovy/graphql/NullValueSupportTest.groovy @@ -10,7 +10,7 @@ import spock.lang.Unroll import static graphql.ExecutionInput.newExecutionInput /* - * Taken from http://facebook.github.io/graphql/#sec-Input-Objects + * Taken from https://spec.graphql.org/October2021/#sec-Input-Objects * * diff --git a/src/test/groovy/graphql/collect/ImmutableKitTest.groovy b/src/test/groovy/graphql/collect/ImmutableKitTest.groovy index e2fb0dfde7..82d76bae1e 100644 --- a/src/test/groovy/graphql/collect/ImmutableKitTest.groovy +++ b/src/test/groovy/graphql/collect/ImmutableKitTest.groovy @@ -13,14 +13,6 @@ class ImmutableKitTest extends Specification { ImmutableKit.emptyMap().size() == 0 } - def "can make an immutable map of lists"() { - when: - ImmutableMap> map = ImmutableKit.toImmutableMapOfLists([a: ["a", "A"]]) - then: - map.get("a") == ImmutableList.copyOf(["a", "A"]) - map.get("a") instanceof ImmutableList - } - def "can map a collections"() { when: def outputList = ImmutableKit.map(["quick", "brown", "fox"], { word -> word.reverse() }) diff --git a/src/test/groovy/graphql/validation/SpecValidation282Test.groovy b/src/test/groovy/graphql/validation/SpecValidation282Test.groovy index 92885e1c56..949f40cedc 100644 --- a/src/test/groovy/graphql/validation/SpecValidation282Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation282Test.groovy @@ -2,7 +2,7 @@ package graphql.validation /** * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * * This test checks that an inline fragment containing just a directive * is parsed correctly diff --git a/src/test/groovy/graphql/validation/SpecValidation51Test.groovy b/src/test/groovy/graphql/validation/SpecValidation51Test.groovy index 78cda9ab7f..ef05fa8063 100644 --- a/src/test/groovy/graphql/validation/SpecValidation51Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation51Test.groovy @@ -1,7 +1,7 @@ package graphql.validation /** * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * @author dwinsor * */ diff --git a/src/test/groovy/graphql/validation/SpecValidation562Test.groovy b/src/test/groovy/graphql/validation/SpecValidation562Test.groovy index bbec32c5d6..7a0f1ada73 100644 --- a/src/test/groovy/graphql/validation/SpecValidation562Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation562Test.groovy @@ -1,7 +1,7 @@ package graphql.validation /** * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * @author dwinsor * */ diff --git a/src/test/groovy/graphql/validation/SpecValidation573Test.groovy b/src/test/groovy/graphql/validation/SpecValidation573Test.groovy index 01b400cc56..0d8565e227 100644 --- a/src/test/groovy/graphql/validation/SpecValidation573Test.groovy +++ b/src/test/groovy/graphql/validation/SpecValidation573Test.groovy @@ -1,7 +1,7 @@ package graphql.validation /** * validation examples used in the spec in given section - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * */ class SpecValidation573Test extends SpecValidationBase { diff --git a/src/test/groovy/graphql/validation/SpecValidationBase.groovy b/src/test/groovy/graphql/validation/SpecValidationBase.groovy index 8d192c8148..1436f47524 100644 --- a/src/test/groovy/graphql/validation/SpecValidationBase.groovy +++ b/src/test/groovy/graphql/validation/SpecValidationBase.groovy @@ -5,7 +5,7 @@ import spock.lang.Specification /** * validation examples used in the spec - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * @author dwinsor * */ diff --git a/src/test/groovy/graphql/validation/SpecValidationSchema.java b/src/test/groovy/graphql/validation/SpecValidationSchema.java index 6e5e196133..398dd1ad3e 100644 --- a/src/test/groovy/graphql/validation/SpecValidationSchema.java +++ b/src/test/groovy/graphql/validation/SpecValidationSchema.java @@ -34,7 +34,7 @@ /** * Sample schema used in the spec for validation examples - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * * @author dwinsor */ diff --git a/src/test/groovy/graphql/validation/SpecValidationSchemaPojos.java b/src/test/groovy/graphql/validation/SpecValidationSchemaPojos.java index 9b0346b3e1..da3d0c7622 100644 --- a/src/test/groovy/graphql/validation/SpecValidationSchemaPojos.java +++ b/src/test/groovy/graphql/validation/SpecValidationSchemaPojos.java @@ -2,7 +2,7 @@ /** * Sample schema pojos used in the spec for validation examples - * http://facebook.github.io/graphql/#sec-Validation + * https://spec.graphql.org/October2021/#sec-Validation * * @author dwinsor */ diff --git a/src/test/groovy/readme/ReadmeExamples.java b/src/test/groovy/readme/ReadmeExamples.java index 78072b3d44..9be296d7bf 100644 --- a/src/test/groovy/readme/ReadmeExamples.java +++ b/src/test/groovy/readme/ReadmeExamples.java @@ -304,7 +304,7 @@ public Review get(DataFetchingEnvironment environment) { // be maps. You can convert them to POJOs inside the data fetcher if that // suits your code better // - // See http://facebook.github.io/graphql/October2016/#sec-Input-Objects + // See https://spec.graphql.org/October2021/#sec-Input-Objects // Map episodeInputMap = environment.getArgument("episode"); Map reviewInputMap = environment.getArgument("review"); From 71eccd499e72a0692860c556dadbb43a37f4b28e Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 31 Oct 2022 17:57:50 +1100 Subject: [PATCH 229/294] Document the new IntrospectionQueryBuilder and tweaked it a little (#3003) --- .../introspection/IntrospectionQuery.java | 5 + .../IntrospectionQueryBuilder.java | 435 ++++++++++-------- 2 files changed, 239 insertions(+), 201 deletions(-) diff --git a/src/main/java/graphql/introspection/IntrospectionQuery.java b/src/main/java/graphql/introspection/IntrospectionQuery.java index 4d372fe992..694ccaab42 100644 --- a/src/main/java/graphql/introspection/IntrospectionQuery.java +++ b/src/main/java/graphql/introspection/IntrospectionQuery.java @@ -4,5 +4,10 @@ @PublicApi public interface IntrospectionQuery { + /** + * This is the default introspection query provided by graphql-java + * + * @see IntrospectionQueryBuilder for ways to customize the introspection query + */ String INTROSPECTION_QUERY = IntrospectionQueryBuilder.build(); } diff --git a/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java index cece9aeec1..b49af872e4 100644 --- a/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java +++ b/src/main/java/graphql/introspection/IntrospectionQueryBuilder.java @@ -1,22 +1,32 @@ package graphql.introspection; import com.google.common.collect.ImmutableList; -import graphql.language.Argument; +import graphql.PublicApi; import graphql.language.AstPrinter; import graphql.language.BooleanValue; import graphql.language.Document; -import graphql.language.Field; -import graphql.language.FragmentDefinition; -import graphql.language.FragmentSpread; import graphql.language.OperationDefinition; import graphql.language.SelectionSet; -import graphql.language.TypeName; import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; +import static graphql.language.Argument.newArgument; +import static graphql.language.Document.newDocument; +import static graphql.language.Field.newField; +import static graphql.language.FragmentDefinition.newFragmentDefinition; +import static graphql.language.FragmentSpread.newFragmentSpread; +import static graphql.language.OperationDefinition.newOperationDefinition; +import static graphql.language.SelectionSet.newSelectionSet; +import static graphql.language.TypeName.newTypeName; +import static java.util.stream.Collectors.toList; + +/** + * {@link IntrospectionQueryBuilder} allows you to build introspection queries controlled + * by the options you specify + */ +@PublicApi public class IntrospectionQueryBuilder { public static class Options { @@ -72,12 +82,12 @@ public int getTypeRefFragmentDepth() { public static Options defaultOptions() { return new Options( - true, - false, - true, - false, - true, - 7 + true, + false, + true, + false, + true, + 7 ); } @@ -90,11 +100,11 @@ public static Options defaultOptions() { */ public Options descriptions(boolean flag) { return new Options(flag, - this.specifiedByUrl, - this.directiveIsRepeatable, - this.schemaDescription, - this.inputValueDeprecation, - this.typeRefFragmentDepth); + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); } /** @@ -106,11 +116,11 @@ public Options descriptions(boolean flag) { */ public Options specifiedByUrl(boolean flag) { return new Options(this.descriptions, - flag, - this.directiveIsRepeatable, - this.schemaDescription, - this.inputValueDeprecation, - this.typeRefFragmentDepth); + flag, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); } /** @@ -122,11 +132,11 @@ public Options specifiedByUrl(boolean flag) { */ public Options directiveIsRepeatable(boolean flag) { return new Options(this.descriptions, - this.specifiedByUrl, - flag, - this.schemaDescription, - this.inputValueDeprecation, - this.typeRefFragmentDepth); + this.specifiedByUrl, + flag, + this.schemaDescription, + this.inputValueDeprecation, + this.typeRefFragmentDepth); } /** @@ -138,11 +148,11 @@ public Options directiveIsRepeatable(boolean flag) { */ public Options schemaDescription(boolean flag) { return new Options(this.descriptions, - this.specifiedByUrl, - this.directiveIsRepeatable, - flag, - this.inputValueDeprecation, - this.typeRefFragmentDepth); + this.specifiedByUrl, + this.directiveIsRepeatable, + flag, + this.inputValueDeprecation, + this.typeRefFragmentDepth); } /** @@ -154,11 +164,11 @@ public Options schemaDescription(boolean flag) { */ public Options inputValueDeprecation(boolean flag) { return new Options(this.descriptions, - this.specifiedByUrl, - this.directiveIsRepeatable, - this.schemaDescription, - flag, - this.typeRefFragmentDepth); + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + flag, + this.typeRefFragmentDepth); } /** @@ -170,187 +180,210 @@ public Options inputValueDeprecation(boolean flag) { */ public Options typeRefFragmentDepth(int typeRefFragmentDepth) { return new Options(this.descriptions, - this.specifiedByUrl, - this.directiveIsRepeatable, - this.schemaDescription, - this.inputValueDeprecation, - typeRefFragmentDepth); + this.specifiedByUrl, + this.directiveIsRepeatable, + this.schemaDescription, + this.inputValueDeprecation, + typeRefFragmentDepth); } } + @SafeVarargs private static List filter(T... args) { - return Arrays.stream(args).filter(Objects::nonNull).collect(Collectors.toList()); + return Arrays.stream(args).filter(Objects::nonNull).collect(toList()); } - public static String build() { - return build(Options.defaultOptions()); - } - - public static String build(Options options) { - SelectionSet schemaSelectionSet = SelectionSet.newSelectionSet().selections( filter( - options.schemaDescription ? Field.newField("description").build() : null, - Field.newField("queryType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("mutationType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("subscriptionType", SelectionSet.newSelectionSet() - .selection( Field.newField("name").build() ) - .build() - ) - .build(), - Field.newField("types", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("FullType").build() ) - .build() - ) - .build(), - Field.newField("directives", SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - options.descriptions ? Field.newField("description").build() : null, - Field.newField("locations").build(), - Field.newField("args") - .arguments( filter( - options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) + /** + * This will build an introspection query in {@link Document} form + * + * @param options the options to use + * + * @return an introspection query in document form + */ + public static Document buildDocument(Options options) { + SelectionSet schemaSelectionSet = newSelectionSet().selections(filter( + options.schemaDescription ? newField("description").build() : null, + newField("queryType", newSelectionSet() + .selection(newField("name").build()) + .build() + ) + .build(), + newField("mutationType", newSelectionSet() + .selection(newField("name").build()) + .build() + ) + .build(), + newField("subscriptionType", newSelectionSet() + .selection(newField("name").build()) + .build() + ) + .build(), + newField("types", newSelectionSet() + .selection(newFragmentSpread("FullType").build()) + .build() + ) + .build(), + newField("directives", newSelectionSet().selections(filter( + newField("name").build(), + options.descriptions ? newField("description").build() : null, + newField("locations").build(), + newField("args") + .arguments(filter( + options.inputValueDeprecation ? newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + )) + .selectionSet(newSelectionSet() + .selection(newFragmentSpread("InputValue").build()) + .build() + ) + .build(), + options.directiveIsRepeatable ? newField("isRepeatable").build() : null + )) + .build() + ) .build() - ) - .build(), - options.directiveIsRepeatable ? Field.newField("isRepeatable").build() : null - ) ) - .build() ) - .build() - ) ).build(); - SelectionSet fullTypeSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build(), - options.descriptions ? Field.newField("description").build() : null, - options.specifiedByUrl ? Field.newField("specifiedByURL").build() : null, - Field.newField("fields") - .arguments( ImmutableList.of( - Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() - ) ) - .selectionSet( SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - options.descriptions ? Field.newField("description").build() : null, - Field.newField("args") - .arguments( filter( - options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) - .build() - ) - .build(), - Field.newField("type", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) + SelectionSet fullTypeSelectionSet = newSelectionSet().selections(filter( + newField("kind").build(), + newField("name").build(), + options.descriptions ? newField("description").build() : null, + options.specifiedByUrl ? newField("specifiedByURL").build() : null, + newField("fields") + .arguments(ImmutableList.of( + newArgument("includeDeprecated", BooleanValue.of(true)).build() + )) + .selectionSet(newSelectionSet().selections(filter( + newField("name").build(), + options.descriptions ? newField("description").build() : null, + newField("args") + .arguments(filter( + options.inputValueDeprecation ? newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + )) + .selectionSet(newSelectionSet() + .selection(newFragmentSpread("InputValue").build()) + .build() + ) + .build(), + newField("type", newSelectionSet() + .selection(newFragmentSpread("TypeRef").build()) + .build() + ) + .build(), + newField("isDeprecated").build(), + newField("deprecationReason").build() + )).build() + ) + .build(), + newField("inputFields") + .arguments(filter( + options.inputValueDeprecation ? newArgument("includeDeprecated", BooleanValue.of(true)).build() : null + )) + .selectionSet(newSelectionSet() + .selection(newFragmentSpread("InputValue").build()) .build() - ) - .build(), - Field.newField("isDeprecated").build(), - Field.newField("deprecationReason").build() - ) ).build() + ) + .build(), + newField("interfaces", newSelectionSet() + .selection(newFragmentSpread("TypeRef").build()) + .build() ) - .build(), - Field.newField("inputFields") - .arguments( filter( - options.inputValueDeprecation ? Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() : null - ) ) - .selectionSet( SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("InputValue").build() ) - .build() + .build(), + newField("enumValues") + .arguments(ImmutableList.of( + newArgument("includeDeprecated", BooleanValue.of(true)).build() + )) + .selectionSet(newSelectionSet().selections(filter( + newField("name").build(), + options.descriptions ? newField("description").build() : null, + newField("isDeprecated").build(), + newField("deprecationReason").build() + )) + .build() + ) + .build(), + newField("possibleTypes", newSelectionSet() + .selection(newFragmentSpread("TypeRef").build()) + .build() ) - .build(), - Field.newField("interfaces", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() + .build() + )).build(); + + SelectionSet inputValueSelectionSet = newSelectionSet().selections(filter( + newField("name").build(), + options.descriptions ? newField("description").build() : null, + newField("type", newSelectionSet() + .selection(newFragmentSpread("TypeRef").build()) + .build() ) - .build(), - Field.newField("enumValues") - .arguments( ImmutableList.of( - Argument.newArgument("includeDeprecated", BooleanValue.of(true)).build() - ) ) - .selectionSet( SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - options.descriptions ? Field.newField("description").build() : null, - Field.newField("isDeprecated").build(), - Field.newField("deprecationReason").build() - ) ) + .build(), + newField("defaultValue").build(), + options.inputValueDeprecation ? newField("isDeprecated").build() : null, + options.inputValueDeprecation ? newField("deprecationReason").build() : null + )).build(); + + SelectionSet typeRefSelectionSet = newSelectionSet().selections(filter( + newField("kind").build(), + newField("name").build() + )).build(); + + for (int i = options.typeRefFragmentDepth; i > 0; i -= 1) { + typeRefSelectionSet = newSelectionSet().selections(filter( + newField("kind").build(), + newField("name").build(), + newField("ofType", typeRefSelectionSet).build() + )).build(); + } + + return newDocument() + .definition(newOperationDefinition() + .operation(OperationDefinition.Operation.QUERY) + .name("IntrospectionQuery") + .selectionSet(newSelectionSet() + .selection(newField("__schema", schemaSelectionSet).build()) + .build() + ) .build() ) - .build(), - Field.newField("possibleTypes", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() + .definition(newFragmentDefinition() + .name("FullType") + .typeCondition(newTypeName().name("__Type").build()) + .selectionSet(fullTypeSelectionSet) + .build() ) - .build() - ) ).build(); - - SelectionSet inputValueSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("name").build(), - options.descriptions ? Field.newField("description").build() : null, - Field.newField("type", SelectionSet.newSelectionSet() - .selection( FragmentSpread.newFragmentSpread("TypeRef").build() ) - .build() + .definition(newFragmentDefinition() + .name("InputValue") + .typeCondition(newTypeName().name("__InputValue").build()) + .selectionSet(inputValueSelectionSet) + .build() ) - .build(), - Field.newField("defaultValue").build(), - options.inputValueDeprecation ? Field.newField("isDeprecated").build() : null, - options.inputValueDeprecation ? Field.newField("deprecationReason").build() : null - ) ).build(); - - SelectionSet typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build() - ) ).build(); - - for(int i=options.typeRefFragmentDepth; i>0; i-=1) { - typeRefSelectionSet = SelectionSet.newSelectionSet().selections( filter( - Field.newField("kind").build(), - Field.newField("name").build(), - Field.newField("ofType", typeRefSelectionSet).build() - ) ).build(); - } - - Document query = Document.newDocument() - .definition( OperationDefinition.newOperationDefinition() - .operation(OperationDefinition.Operation.QUERY) - .name("IntrospectionQuery") - .selectionSet( SelectionSet.newSelectionSet() - .selection( Field.newField("__schema", schemaSelectionSet).build() ) - .build() + .definition(newFragmentDefinition() + .name("TypeRef") + .typeCondition(newTypeName().name("__Type").build()) + .selectionSet(typeRefSelectionSet) + .build() ) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("FullType") - .typeCondition( TypeName.newTypeName().name("__Type").build() ) - .selectionSet(fullTypeSelectionSet) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("InputValue") - .typeCondition( TypeName.newTypeName().name("__InputValue").build() ) - .selectionSet(inputValueSelectionSet) - .build() - ) - .definition( FragmentDefinition.newFragmentDefinition() - .name("TypeRef") - .typeCondition( TypeName.newTypeName().name("__Type").build() ) - .selectionSet(typeRefSelectionSet) - .build() - ) - .build(); - - return AstPrinter.printAst(query); + .build(); + } + + /** + * This will build an introspection query in {@link String} form based on the options you provide + * + * @param options the options to use + * + * @return an introspection query in string form + */ + + public static String build(Options options) { + return AstPrinter.printAst(buildDocument(options)); + } + + /** + * This will build a default introspection query in {@link String} form + * + * @return an introspection query in string form + */ + public static String build() { + return build(Options.defaultOptions()); } -} +} \ No newline at end of file From 97874976a0a05a318a1c1dc39fdfbf76fb4ab232 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 31 Oct 2022 18:03:09 +1100 Subject: [PATCH 230/294] LambdaMetafactory support for property fetches (#2985) * LambdaMetafactory support Another PR on improving getter access * LambdaMetafactory support Fixed test where parameters are present * LambdaMetafactory support Benchmark fix up * final fields * Tweaked code from PR feedback --- .../graphql/schema/PropertyFetchingImpl.java | 59 ++++-- .../fetching/LambdaFetchingSupport.java | 193 ++++++++++++++++++ .../schema/PropertyDataFetcherTest.groovy | 9 +- .../fetching/LambdaFetchingSupportTest.groovy | 93 +++++++++ .../groovy/graphql/schema/fetching/Pojo.java | 68 ++++++ .../java/benchmark/GetterAccessBenchmark.java | 71 +++++++ 6 files changed, 477 insertions(+), 16 deletions(-) create mode 100644 src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java create mode 100644 src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy create mode 100644 src/test/groovy/graphql/schema/fetching/Pojo.java create mode 100644 src/test/java/benchmark/GetterAccessBenchmark.java diff --git a/src/main/java/graphql/schema/PropertyFetchingImpl.java b/src/main/java/graphql/schema/PropertyFetchingImpl.java index a671388d3b..298063cbc4 100644 --- a/src/main/java/graphql/schema/PropertyFetchingImpl.java +++ b/src/main/java/graphql/schema/PropertyFetchingImpl.java @@ -2,6 +2,7 @@ import graphql.GraphQLException; import graphql.Internal; +import graphql.schema.fetching.LambdaFetchingSupport; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -15,6 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import java.util.function.Predicate; import static graphql.Assert.assertShouldNeverHappen; @@ -30,6 +32,7 @@ public class PropertyFetchingImpl { private final AtomicBoolean USE_SET_ACCESSIBLE = new AtomicBoolean(true); private final AtomicBoolean USE_NEGATIVE_CACHE = new AtomicBoolean(true); + private final ConcurrentMap LAMBDA_CACHE = new ConcurrentHashMap<>(); private final ConcurrentMap METHOD_CACHE = new ConcurrentHashMap<>(); private final ConcurrentMap FIELD_CACHE = new ConcurrentHashMap<>(); private final ConcurrentMap NEGATIVE_CACHE = new ConcurrentHashMap<>(); @@ -39,15 +42,22 @@ public PropertyFetchingImpl(Class singleArgumentType) { this.singleArgumentType = singleArgumentType; } - private class CachedMethod { - Method method; - boolean takesSingleArgumentTypeAsOnlyArgument; + private final class CachedMethod { + private final Method method; + private final boolean takesSingleArgumentTypeAsOnlyArgument; CachedMethod(Method method) { this.method = method; this.takesSingleArgumentTypeAsOnlyArgument = takesSingleArgumentTypeAsOnlyArgument(method); } + } + + private static final class CachedLambdaFunction { + private final Function getter; + CachedLambdaFunction(Function getter) { + this.getter = getter; + } } public Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType, Object singleArgumentValue) { @@ -56,8 +66,13 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g } CacheKey cacheKey = mkCacheKey(object, propertyName); - // lets try positive cache mechanisms first. If we have seen the method or field before + + // let's try positive cache mechanisms first. If we have seen the method or field before // then we invoke it directly without burning any cycles doing reflection. + CachedLambdaFunction cachedFunction = LAMBDA_CACHE.get(cacheKey); + if (cachedFunction != null) { + return cachedFunction.getter.apply(object); + } CachedMethod cachedMethod = METHOD_CACHE.get(cacheKey); if (cachedMethod != null) { try { @@ -72,9 +87,9 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g } // - // if we have tried all strategies before and they have all failed then we negatively cache + // if we have tried all strategies before, and they have all failed then we negatively cache // the cacheKey and assume that it's never going to turn up. This shortcuts the property lookup - // in systems where there was a `foo` graphql property but they never provided an POJO + // in systems where there was a `foo` graphql property, but they never provided an POJO // version of `foo`. // // we do this second because we believe in the positive cached version will mostly prevail @@ -83,10 +98,20 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g if (isNegativelyCached(cacheKey)) { return null; } + // - // ok we haven't cached it and we haven't negatively cached it so we have to find the POJO method which is the most + // ok we haven't cached it, and we haven't negatively cached it, so we have to find the POJO method which is the most // expensive operation here // + + Optional> getterOpt = LambdaFetchingSupport.createGetter(object.getClass(), propertyName); + if (getterOpt.isPresent()) { + Function getter = getterOpt.get(); + cachedFunction = new CachedLambdaFunction(getter); + LAMBDA_CACHE.putIfAbsent(cacheKey, cachedFunction); + return getter.apply(object); + } + boolean dfeInUse = singleArgumentValue != null; try { MethodFinder methodFinder = (root, methodName) -> findPubliclyAccessibleMethod(cacheKey, root, methodName, dfeInUse); @@ -99,7 +124,7 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g try { return getPropertyViaFieldAccess(cacheKey, object, propertyName); } catch (FastNoSuchMethodException e) { - // we have nothing to ask for and we have exhausted our lookup strategies + // we have nothing to ask for, and we have exhausted our lookup strategies putInNegativeCache(cacheKey); return null; } @@ -272,6 +297,7 @@ private boolean isBooleanProperty(GraphQLType graphQLType) { } public void clearReflectionCache() { + LAMBDA_CACHE.clear(); METHOD_CACHE.clear(); FIELD_CACHE.clear(); NEGATIVE_CACHE.clear(); @@ -304,8 +330,12 @@ private CacheKey(ClassLoader classLoader, String className, String propertyName) @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CacheKey)) return false; + if (this == o) { + return true; + } + if (!(o instanceof CacheKey)) { + return false; + } CacheKey cacheKey = (CacheKey) o; return Objects.equals(classLoader, cacheKey.classLoader) && Objects.equals(className, cacheKey.className) && Objects.equals(propertyName, cacheKey.propertyName); } @@ -322,10 +352,10 @@ public int hashCode() { @Override public String toString() { return "CacheKey{" + - "classLoader=" + classLoader + - ", className='" + className + '\'' + - ", propertyName='" + propertyName + '\'' + - '}'; + "classLoader=" + classLoader + + ", className='" + className + '\'' + + ", propertyName='" + propertyName + '\'' + + '}'; } } @@ -343,7 +373,6 @@ private static Comparator mostMethodArgsFirst() { return Comparator.comparingInt(Method::getParameterCount).reversed(); } - @SuppressWarnings("serial") private static class FastNoSuchMethodException extends NoSuchMethodException { public FastNoSuchMethodException(String methodName) { super(methodName); diff --git a/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java new file mode 100644 index 0000000000..9095c66c08 --- /dev/null +++ b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java @@ -0,0 +1,193 @@ +package graphql.schema.fetching; + +import graphql.Internal; +import graphql.VisibleForTesting; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import static java.util.stream.Collectors.toList; + +@Internal +public class LambdaFetchingSupport { + + + /** + * This support class will use {@link LambdaMetafactory} and {@link MethodHandles} to create a dynamic function that allows access to a public + * getter method on the nominated class. {@link MethodHandles} is a caller senstive lookup mechanism. If the graphql-java cant lookup a class, then + * it won't be able to make dynamic lambda function to it. + *

+ * If one cant be made, because it doesn't exist or the calling class does not have access to the method, then it will return + * an empty result indicating that this strategy cant be used. + * + * @param sourceClass the class that has the property getter method + * @param propertyName the name of the property to get + * + * @return a function that can be used to pass in an instance of source class and returns its getter method value + */ + public static Optional> createGetter(Class sourceClass, String propertyName) { + Method candidateMethod = getCandidateMethod(sourceClass, propertyName); + if (candidateMethod != null) { + try { + Function getterFunction = mkCallFunction(sourceClass, candidateMethod.getName(), candidateMethod.getReturnType()); + return Optional.of(getterFunction); + } catch (Throwable ignore) { + // if we cant make a dynamic lambda here, then we give up and let the old property fetching code do its thing + } + } + return Optional.empty(); + } + + + private static Method getCandidateMethod(Class sourceClass, String propertyName) { + List allGetterMethods = findGetterMethodsForProperty(sourceClass, propertyName); + List pojoGetterMethods = allGetterMethods.stream() + .filter(LambdaFetchingSupport::isPossiblePojoMethod) + .collect(toList()); + if (!pojoGetterMethods.isEmpty()) { + Method method = pojoGetterMethods.get(0); + if (isBooleanGetter(method)) { + method = findBestBooleanGetter(pojoGetterMethods); + } + return checkForSingleParameterPeer(method, allGetterMethods); + } else { + return null; + } + + } + + private static Method checkForSingleParameterPeer(Method candidateMethod, List allMethods) { + // getFoo(DataFetchingEnv ev) is allowed, but we don't want to handle it in this class + // so this find those edge cases + for (Method allMethod : allMethods) { + if (allMethod.getParameterCount() > 0) { + // we have some method with the property name that takes more than 1 argument + // we don't want to handle this here, so we are saying there is one + return null; + } + } + return candidateMethod; + } + + private static Method findBestBooleanGetter(List methods) { + // we prefer isX() over getX() if both happen to be present + Optional isMethod = methods.stream().filter(method -> method.getName().startsWith("is")).findFirst(); + return isMethod.orElse(methods.get(0)); + } + + /** + * Finds all methods in a class hierarchy that match the property name - they might not be suitable but they + * + * @param sourceClass the class we are looking to work on + * @param propertyName the name of the property + * + * @return a list of getter methods for that property + */ + private static List findGetterMethodsForProperty(Class sourceClass, String propertyName) { + List methods = new ArrayList<>(); + Class currentClass = sourceClass; + while (currentClass != null) { + Method[] declaredMethods = currentClass.getDeclaredMethods(); + for (Method declaredMethod : declaredMethods) { + if (isGetterNamed(declaredMethod)) { + if (nameMatches(propertyName, declaredMethod)) { + methods.add(declaredMethod); + } + } + } + currentClass = currentClass.getSuperclass(); + } + + return methods.stream() + .sorted(Comparator.comparing(Method::getName)) + .collect(toList()); + } + + + private static boolean nameMatches(String propertyName, Method declaredMethod) { + String methodPropName = mkPropertyName(declaredMethod); + return propertyName.equals(methodPropName); + } + + private static boolean isPossiblePojoMethod(Method method) { + return !isObjectMethod(method) && + returnsSomething(method) && + isGetterNamed(method) && + hasNoParameters(method) && + isPublic(method); + } + + private static boolean isBooleanGetter(Method method) { + Class returnType = method.getReturnType(); + return isGetterNamed(method) && (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)); + } + + private static boolean hasNoParameters(Method method) { + return method.getParameterCount() == 0; + } + + private static boolean isGetterNamed(Method method) { + String name = method.getName(); + return ((name.startsWith("get") && name.length() > 4) || (name.startsWith("is") && name.length() > 3)); + } + + private static boolean returnsSomething(Method method) { + return !method.getReturnType().equals(Void.class); + } + + private static boolean isPublic(Method method) { + return Modifier.isPublic(method.getModifiers()); + } + + private static boolean isObjectMethod(Method method) { + return method.getDeclaringClass().equals(Object.class); + } + + private static String mkPropertyName(Method method) { + // + // getFooName becomes fooName + // isFoo becomes foo + // + String name = method.getName(); + if (name.startsWith("get")) { + name = name.substring(3); + } else if (name.startsWith("is")) { + name = name.substring(2); + } + return decapitalize(name); + } + + private static String decapitalize(String name) { + if (name.length() == 0) { + return name; + } + return name.substring(0, 1).toLowerCase() + name.substring(1); + } + + + @VisibleForTesting + static Function mkCallFunction(Class targetClass, String targetMethod, Class targetMethodReturnType) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle virtualMethodHandle = lookup.findVirtual(targetClass, targetMethod, MethodType.methodType(targetMethodReturnType)); + CallSite site = LambdaMetafactory.metafactory(lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + virtualMethodHandle, + MethodType.methodType(targetMethodReturnType, targetClass)); + @SuppressWarnings("unchecked") + Function getterFunction = (Function) site.getTarget().invokeExact(); + return getterFunction; + } + +} diff --git a/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy b/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy index 523f0e0dad..39f83fa123 100644 --- a/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy +++ b/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy @@ -106,9 +106,16 @@ class PropertyDataFetcherTest extends Specification { def "fetch via public method declared two classes up"() { def environment = env(new TwoClassesDown("aValue")) def fetcher = new PropertyDataFetcher("publicProperty") + when: def result = fetcher.get(environment) - expect: + then: result == "publicValue" + + when: + result = fetcher.get(environment) + then: + result == "publicValue" + } def "fetch via property only defined on package protected impl"() { diff --git a/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy new file mode 100644 index 0000000000..19b78cbd03 --- /dev/null +++ b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy @@ -0,0 +1,93 @@ +package graphql.schema.fetching + +import spock.lang.Specification + +class LambdaFetchingSupportTest extends Specification { + + def "can proxy Pojo methods"() { + + def pojo = new Pojo("Brad", 42) + when: + def getName = LambdaFetchingSupport.mkCallFunction(Pojo.class, "getName", String.class) + def getAge = LambdaFetchingSupport.mkCallFunction(Pojo.class, "getAge", Integer.TYPE) + + then: + getName.apply(pojo) == "Brad" + getAge.apply(pojo) == 42 + } + + def "get make getters based on property names"() { + def pojo = new Pojo("Brad", 42) + when: + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "name") + then: + getter.isPresent() + getter.get().apply(pojo) == "Brad" + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "age") + then: + getter.isPresent() + getter.get().apply(pojo) == 42 + + } + + def "will handle bad methods and missing ones"() { + + when: + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "nameX") + then: + !getter.isPresent() + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "get") + then: + !getter.isPresent() + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "is") + then: + !getter.isPresent() + + } + + def "can handle boolean setters - is by preference"() { + + def pojo = new Pojo("Brad", 42) + when: + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "interesting") + then: + getter.isPresent() + getter.get().apply(pojo) == true + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "alone") + then: + getter.isPresent() + getter.get().apply(pojo) == true + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "booleanAndNullish") + then: + getter.isPresent() + getter.get().apply(pojo) == null + } + + def "will ignore non public methods"() { + + when: + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "protectedLevelMethod") + then: + !getter.isPresent() + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "privateLevelMethod") + then: + !getter.isPresent() + + when: + getter = LambdaFetchingSupport.createGetter(Pojo.class, "packageLevelMethod") + then: + !getter.isPresent() + } +} diff --git a/src/test/groovy/graphql/schema/fetching/Pojo.java b/src/test/groovy/graphql/schema/fetching/Pojo.java new file mode 100644 index 0000000000..3b478f0712 --- /dev/null +++ b/src/test/groovy/graphql/schema/fetching/Pojo.java @@ -0,0 +1,68 @@ +package graphql.schema.fetching; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +public class Pojo { + final String name; + final int age; + + public Pojo(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public Integer getHeight() { + return null; + } + + public List getOtherNames() { + return ImmutableList.of("A", "B"); + } + + protected String protectedLevelMethod() { + return "protectedLevelMethod"; + } + + private String privateLevelMethod() { + return "privateLevelMethod"; + } + + String packageLevelMethod() { + return "packageLevelMethod"; + } + + public boolean getInteresting() { + return false; + } + + public boolean isInteresting() { + return true; + } + + public Boolean getAlone() { + return true; + } + + public Boolean getBooleanAndNullish() { + return null; + } + + public String get() { + return "get"; + } + + public String is() { + return "is"; + } + +} \ No newline at end of file diff --git a/src/test/java/benchmark/GetterAccessBenchmark.java b/src/test/java/benchmark/GetterAccessBenchmark.java new file mode 100644 index 0000000000..7dd8c3d719 --- /dev/null +++ b/src/test/java/benchmark/GetterAccessBenchmark.java @@ -0,0 +1,71 @@ +package benchmark; + +import graphql.schema.fetching.LambdaFetchingSupport; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +@Warmup(iterations = 2, time = 2, batchSize = 3) +@Measurement(iterations = 3, time = 2, batchSize = 4) +public class GetterAccessBenchmark { + + public static class Pojo { + final String name; + final int age; + + public Pojo(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + } + + static Pojo pojo = new Pojo("Brad", 42); + + static Function getter = LambdaFetchingSupport.createGetter(Pojo.class, "name").get(); + + static Method getterMethod; + + static { + try { + getterMethod = Pojo.class.getMethod("getName"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + + @Benchmark + public void measureDirectAccess(Blackhole bh) { + Object name = pojo.getName(); + bh.consume(name); + } + + @Benchmark + public void measureLambdaAccess(Blackhole bh) { + Object value = getter.apply(pojo); + bh.consume(value); + } + + @Benchmark + public void measureReflectionAccess(Blackhole bh) { + try { + Object name = getterMethod.invoke(pojo); + bh.consume(name); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } +} From 13ab0009bd42b591bb4a542cd9c780a53515321d Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Mon, 31 Oct 2022 18:03:56 +1100 Subject: [PATCH 231/294] SchemaGeneratorPostProcessing should be deprecated (#2999) * SchemaGeneratorPostProcessing should be deprecated * SchemaGeneratorPostProcessing should be deprecated - right date --- .../graphql/schema/idl/RuntimeWiring.java | 5 +++++ ...veWiringSchemaGeneratorPostProcessing.java | 3 +-- .../graphql/schema/idl/SchemaGenerator.java | 20 +++++++++---------- .../idl/SchemaGeneratorPostProcessing.java | 6 ++++++ .../schema/idl/SchemaGeneratorTest.groovy | 4 ++-- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/java/graphql/schema/idl/RuntimeWiring.java b/src/main/java/graphql/schema/idl/RuntimeWiring.java index 2b327d804b..69ba9f6633 100644 --- a/src/main/java/graphql/schema/idl/RuntimeWiring.java +++ b/src/main/java/graphql/schema/idl/RuntimeWiring.java @@ -1,5 +1,6 @@ package graphql.schema.idl; +import graphql.DeprecatedAt; import graphql.PublicApi; import graphql.schema.DataFetcher; import graphql.schema.GraphQLCodeRegistry; @@ -352,7 +353,11 @@ public Builder comparatorRegistry(GraphqlTypeComparatorRegistry comparatorRegist * @param schemaGeneratorPostProcessing the non null schema transformer to add * * @return the runtime wiring builder + * @deprecated This mechanism can be achieved in a better way via {@link graphql.schema.SchemaTransformer} + * after the schema is built */ + @Deprecated + @DeprecatedAt(value = "2022-10-29") public Builder transformer(SchemaGeneratorPostProcessing schemaGeneratorPostProcessing) { this.schemaGeneratorPostProcessings.add(assertNotNull(schemaGeneratorPostProcessing)); return this; diff --git a/src/main/java/graphql/schema/idl/SchemaDirectiveWiringSchemaGeneratorPostProcessing.java b/src/main/java/graphql/schema/idl/SchemaDirectiveWiringSchemaGeneratorPostProcessing.java index 9d8a5b2afe..52f8badbb4 100644 --- a/src/main/java/graphql/schema/idl/SchemaDirectiveWiringSchemaGeneratorPostProcessing.java +++ b/src/main/java/graphql/schema/idl/SchemaDirectiveWiringSchemaGeneratorPostProcessing.java @@ -25,7 +25,7 @@ import static graphql.util.TraversalControl.CONTINUE; @Internal -class SchemaDirectiveWiringSchemaGeneratorPostProcessing implements SchemaGeneratorPostProcessing { +class SchemaDirectiveWiringSchemaGeneratorPostProcessing { private final SchemaGeneratorDirectiveHelper generatorDirectiveHelper = new SchemaGeneratorDirectiveHelper(); private final TypeDefinitionRegistry typeRegistry; @@ -41,7 +41,6 @@ public SchemaDirectiveWiringSchemaGeneratorPostProcessing(TypeDefinitionRegistry } - @Override public GraphQLSchema process(GraphQLSchema originalSchema) { codeRegistryBuilder.trackChanges(); Visitor visitor = new Visitor(); diff --git a/src/main/java/graphql/schema/idl/SchemaGenerator.java b/src/main/java/graphql/schema/idl/SchemaGenerator.java index f664e4adad..bb74a7a75a 100644 --- a/src/main/java/graphql/schema/idl/SchemaGenerator.java +++ b/src/main/java/graphql/schema/idl/SchemaGenerator.java @@ -9,7 +9,6 @@ import graphql.schema.GraphQLType; import graphql.schema.idl.errors.SchemaProblem; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -116,22 +115,23 @@ private GraphQLSchema makeExecutableSchemaImpl(TypeDefinitionRegistry typeRegist }); GraphQLSchema graphQLSchema = schemaBuilder.build(); - List schemaTransformers = new ArrayList<>(); + // we check if there are any SchemaDirectiveWiring's in play and if there are // we add this to enable them. By not adding it always, we save unnecessary // schema build traversals if (buildCtx.isDirectiveWiringRequired()) { // handle directive wiring AFTER the schema has been built and hence type references are resolved at callback time - schemaTransformers.add( - new SchemaDirectiveWiringSchemaGeneratorPostProcessing( - buildCtx.getTypeRegistry(), - buildCtx.getWiring(), - buildCtx.getCodeRegistry()) - ); + SchemaDirectiveWiringSchemaGeneratorPostProcessing directiveWiringProcessing = new SchemaDirectiveWiringSchemaGeneratorPostProcessing( + buildCtx.getTypeRegistry(), + buildCtx.getWiring(), + buildCtx.getCodeRegistry()); + graphQLSchema = directiveWiringProcessing.process(graphQLSchema); } - schemaTransformers.addAll(buildCtx.getWiring().getSchemaGeneratorPostProcessings()); - for (SchemaGeneratorPostProcessing postProcessing : schemaTransformers) { + // + // SchemaGeneratorPostProcessing is deprecated but for now we continue to run them + // + for (SchemaGeneratorPostProcessing postProcessing : buildCtx.getWiring().getSchemaGeneratorPostProcessings()) { graphQLSchema = postProcessing.process(graphQLSchema); } return graphQLSchema; diff --git a/src/main/java/graphql/schema/idl/SchemaGeneratorPostProcessing.java b/src/main/java/graphql/schema/idl/SchemaGeneratorPostProcessing.java index 790f537c24..906d187f28 100644 --- a/src/main/java/graphql/schema/idl/SchemaGeneratorPostProcessing.java +++ b/src/main/java/graphql/schema/idl/SchemaGeneratorPostProcessing.java @@ -1,13 +1,19 @@ package graphql.schema.idl; +import graphql.DeprecatedAt; import graphql.PublicSpi; import graphql.schema.GraphQLSchema; /** * These are called by the {@link SchemaGenerator} after a valid schema has been built * and they can then adjust it accordingly with some sort of post processing. + * + * @deprecated This mechanism can be achieved in a better way via {@link graphql.schema.SchemaTransformer} + * after the schema is built */ @PublicSpi +@Deprecated +@DeprecatedAt(value = "2022-10-29") public interface SchemaGeneratorPostProcessing { /** diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 2d0e4480fa..a8e9ce3617 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -1831,14 +1831,14 @@ class SchemaGeneratorTest extends Specification { def extraDirective = (GraphQLDirective.newDirective()).name("extra") .argument(GraphQLArgument.newArgument().name("value").type(GraphQLString)).build() - def transformer = new SchemaGeneratorPostProcessing() { + def transformer = new SchemaGeneratorPostProcessing() { // Retained to show deprecated code is still run @Override GraphQLSchema process(GraphQLSchema originalSchema) { originalSchema.transform({ builder -> builder.additionalDirective(extraDirective) }) } } def wiring = RuntimeWiring.newRuntimeWiring() - .transformer(transformer) + .transformer(transformer) // Retained to show deprecated code is still run .build() GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(types, wiring) expect: From e9b97b62731d80f29da8ce3f4685b57fa8f9fdcf Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 4 Nov 2022 09:42:26 +1000 Subject: [PATCH 232/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 6 ++++ .../schema/diffing/ana/SchemaDifference.java | 16 ++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 29 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 01c6f89060..b573226aaa 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -9,6 +9,7 @@ import graphql.schema.diffing.SchemaGraph; import graphql.schema.diffing.Vertex; import graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentRename; +import graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectLocation; import graphql.schema.idl.ScalarInfo; import java.util.LinkedHashMap; @@ -217,6 +218,11 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } + }else if(container.isOfType(SchemaGraph.OBJECT)) { + Vertex object = container; + AppliedDirectiveObjectLocation location = new AppliedDirectiveObjectLocation(object.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 4159f61e43..ae960cb153 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1118,11 +1118,27 @@ class AppliedDirectiveSchemaLocation implements AppliedDirectiveLocationDetail { } class AppliedDirectiveObjectLocation implements AppliedDirectiveLocationDetail { + private final String name; + + public AppliedDirectiveObjectLocation(String name) { + this.name = name; + } + public String getName() { + return name; + } } class AppliedDirectiveInterfaceLocation implements AppliedDirectiveLocationDetail { + private final String name; + + public AppliedDirectiveInterfaceLocation(String name) { + this.name = name; + } + public String getName() { + return name; + } } class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index a568426cb4..ed7afcd183 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -4,6 +4,7 @@ import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification +import static graphql.schema.diffing.ana.SchemaDifference.* import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation @@ -1485,6 +1486,31 @@ class EditOperationAnalyzerTest extends Specification { argTypeModification[0].newType == '[String]!' } + def "object added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on OBJECT + + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on OBJECT + + type Query @d(arg: "foo") { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectLocation).name == "Query" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' @@ -1559,7 +1585,7 @@ class EditOperationAnalyzerTest extends Specification { def changes = calcDiff(oldSdl, newSdl) then: changes.objectDifferences["Query"] instanceof ObjectModification - def argumentRenames = (changes.objectDifferences["Query"] as ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentRename) + def argumentRenames = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentRename) def location = argumentRenames[0].locationDetail as AppliedDirectiveObjectFieldLocation location.objectName == "Query" location.fieldName == "foo" @@ -1568,6 +1594,7 @@ class EditOperationAnalyzerTest extends Specification { } + EditOperationAnalysisResult calcDiff( String oldSdl, String newSdl From 9ab70cd9d3910065f118797bbb3c7f97fe7c3f14 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 4 Nov 2022 09:53:47 +1000 Subject: [PATCH 233/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 8 ++++- .../schema/diffing/ana/SchemaDifference.java | 2 +- .../ana/EditOperationAnalyzerTest.groovy | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b573226aaa..43a70e1f93 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -17,6 +17,7 @@ import java.util.Map; import static graphql.Assert.assertTrue; +import static graphql.schema.diffing.ana.SchemaDifference.*; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation; @@ -218,11 +219,16 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } - }else if(container.isOfType(SchemaGraph.OBJECT)) { + } else if (container.isOfType(SchemaGraph.OBJECT)) { Vertex object = container; AppliedDirectiveObjectLocation location = new AppliedDirectiveObjectLocation(object.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.INTERFACE)) { + Vertex interfaze = container; + AppliedDirectiveInterfaceLocation location = new AppliedDirectiveInterfaceLocation(interfaze.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveAddition); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index ae960cb153..c9587cc2ff 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1165,7 +1165,7 @@ class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocati } - class AppliedDirectiveAddition implements ObjectModificationDetail { + class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ed7afcd183..87240b67dd 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1511,6 +1511,37 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "interface added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INTERFACE + + type Query implements I{ + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on INTERFACE + + type Query implements I { + foo: String + } + interface I @d(arg: "foo") { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceLocation).name == "I" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' From a2ac1e64e6d674d492fc720a99e95cbc10b59c91 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 4 Nov 2022 10:19:40 +1000 Subject: [PATCH 234/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 32 +++++++++++++++++++ .../schema/diffing/ana/SchemaDifference.java | 29 ++++++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 25 +++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 43a70e1f93..413da7dba8 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -215,20 +215,41 @@ private void appliedDirectiveAdded(EditOperation editOperation) { Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { Vertex object = interfaceOrObjective; + + if (isObjectAdded(object.getName())) { + return; + } + if (isFieldNewForExistingObject(object.getName(), field.getName())) { + return; + } AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } } else if (container.isOfType(SchemaGraph.OBJECT)) { Vertex object = container; + if (isObjectAdded(object.getName())) { + return; + } AppliedDirectiveObjectLocation location = new AppliedDirectiveObjectLocation(object.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } else if (container.isOfType(SchemaGraph.INTERFACE)) { Vertex interfaze = container; + if (isInterfaceAdded(interfaze.getName())) { + return; + } AppliedDirectiveInterfaceLocation location = new AppliedDirectiveInterfaceLocation(interfaze.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.SCALAR)) { + Vertex scalar = container; + if (isScalarAdded(scalar.getName())) { + return; + } + AppliedDirectiveScalarLocation location = new AppliedDirectiveScalarLocation(scalar.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getScalarModification(scalar.getName()).getDetails().add(appliedDirectiveAddition); } } @@ -966,6 +987,9 @@ private boolean isInterfaceDeleted(String name) { private boolean isInterfaceAdded(String name) { return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceAddition; } + private boolean isScalarAdded(String name) { + return scalarDifferences.containsKey(name) && scalarDifferences.get(name) instanceof ScalarAddition; + } private ObjectModification getObjectModification(String newName) { if (!objectDifferences.containsKey(newName)) { @@ -1007,6 +1031,14 @@ private InterfaceModification getInterfaceModification(String newName) { return (InterfaceModification) interfaceDifferences.get(newName); } + private ScalarModification getScalarModification(String newName) { + if (!scalarDifferences.containsKey(newName)) { + scalarDifferences.put(newName, new ScalarModification(newName)); + } + assertTrue(scalarDifferences.get(newName) instanceof ScalarModification); + return (ScalarModification) scalarDifferences.get(newName); + } + private void addedObject(EditOperation editOperation) { String objectName = editOperation.getTargetVertex().getName(); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index c9587cc2ff..dd6ff502e7 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -868,10 +868,15 @@ public String getName() { } } + interface ScalarModificationDetail { + + } + class ScalarModification implements SchemaModification, ScalarDifference { private final String oldName; private final String newName; private final boolean nameChanged; + private List details = new ArrayList<>(); public ScalarModification(String oldName, String newName) { @@ -879,6 +884,12 @@ public ScalarModification(String oldName, String newName) { this.newName = newName; this.nameChanged = oldName.equals(newName); } + public ScalarModification( String newName) { + this.oldName = newName; + this.newName = newName; + this.nameChanged = false; + } + public boolean isNameChanged() { return nameChanged; @@ -891,6 +902,14 @@ public String getNewName() { public String getOldName() { return oldName; } + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + } //------Directive @@ -1110,7 +1129,15 @@ public AppliedDirectiveInterfaceFieldLocation(String interfaceName, String field } class AppliedDirectiveScalarLocation implements AppliedDirectiveLocationDetail { + private final String name; + + public AppliedDirectiveScalarLocation(String name) { + this.name = name; + } + public String getName() { + return name; + } } class AppliedDirectiveSchemaLocation implements AppliedDirectiveLocationDetail { @@ -1165,7 +1192,7 @@ class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocati } - class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail { + class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail, ScalarModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 87240b67dd..937760a753 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1542,6 +1542,31 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "scalar added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime + type Query { + foo: DateTime + } + ''' + def newSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime @d(arg: "foo") + type Query { + foo: DateTime + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.scalarDifferences["DateTime"] instanceof ScalarModification + def appliedDirective = (changes.scalarDifferences["DateTime"] as ScalarModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveScalarLocation).name == "DateTime" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' From be6b7afea1e3c1c05713cbd543d5a29b277a42ef Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 4 Nov 2022 11:04:10 +1000 Subject: [PATCH 235/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 11 +++++++- .../schema/diffing/ana/SchemaDifference.java | 27 ++++++++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 26 +++++++++++++++++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 413da7dba8..5fdc7a2734 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -250,6 +250,14 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveScalarLocation location = new AppliedDirectiveScalarLocation(scalar.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getScalarModification(scalar.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.ENUM)) { + Vertex enumVertex = container; + if (isEnumAdded(enumVertex.getName())) { + return; + } + AppliedDirectiveEnumLocation location = new AppliedDirectiveEnumLocation(enumVertex.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveAddition); } } @@ -987,6 +995,7 @@ private boolean isInterfaceDeleted(String name) { private boolean isInterfaceAdded(String name) { return interfaceDifferences.containsKey(name) && interfaceDifferences.get(name) instanceof InterfaceAddition; } + private boolean isScalarAdded(String name) { return scalarDifferences.containsKey(name) && scalarDifferences.get(name) instanceof ScalarAddition; } @@ -1031,7 +1040,7 @@ private InterfaceModification getInterfaceModification(String newName) { return (InterfaceModification) interfaceDifferences.get(newName); } - private ScalarModification getScalarModification(String newName) { + private ScalarModification getScalarModification(String newName) { if (!scalarDifferences.containsKey(newName)) { scalarDifferences.put(newName, new ScalarModification(newName)); } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index dd6ff502e7..e2801de8be 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1173,11 +1173,27 @@ class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail } class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail { + private final String name; + + public AppliedDirectiveUnionLocation(String name) { + this.name = name; + } + public String getName() { + return name; + } } class AppliedDirectiveEnumLocation implements AppliedDirectiveLocationDetail { + private final String name; + + public AppliedDirectiveEnumLocation(String name) { + this.name = name; + } + public String getName() { + return name; + } } class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetail { @@ -1185,14 +1201,23 @@ class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetai } class AppliedDirectiveInputObjectLocation implements AppliedDirectiveLocationDetail { + private final String name; + public AppliedDirectiveInputObjectLocation(String name) { + this.name = name; + } + + public String getName() { + return name; + } } + class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocationDetail { } - class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail, ScalarModificationDetail { + class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail, ScalarModificationDetail, EnumModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 937760a753..ea86bcc2f8 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1567,6 +1567,31 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "enum added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM + enum E { A, B } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM + enum E @d(arg: "foo") { A, B } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumLocation).name == "E" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' @@ -1650,7 +1675,6 @@ class EditOperationAnalyzerTest extends Specification { } - EditOperationAnalysisResult calcDiff( String oldSdl, String newSdl From 03c8592730bdfe118c6e7e1ccda44b3018e8ddfc Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 4 Nov 2022 11:24:52 +1000 Subject: [PATCH 236/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 20 ++++++++++++ .../schema/diffing/ana/SchemaDifference.java | 31 +++++++++++++++++-- .../ana/EditOperationAnalyzerTest.groovy | 29 +++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 5fdc7a2734..b4a6a67f72 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -258,6 +258,14 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveEnumLocation location = new AppliedDirectiveEnumLocation(enumVertex.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.INPUT_OBJECT)) { + Vertex inputObject = container; + if (isInputObjectAdded(inputObject.getName())) { + return; + } + AppliedDirectiveInputObjectLocation location = new AppliedDirectiveInputObjectLocation(inputObject.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getInputObjectLocation(inputObject.getName()).getDetails().add(appliedDirectiveAddition); } } @@ -910,6 +918,10 @@ private boolean isEnumAdded(String name) { return enumDifferences.containsKey(name) && enumDifferences.get(name) instanceof EnumAddition; } + private boolean isInputObjectAdded(String name) { + return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectAddition; + } + private boolean isArgumentNewForExistingDirective(String directiveName, String argumentName) { if (!directiveDifferences.containsKey(directiveName)) { return false; @@ -1024,6 +1036,14 @@ private EnumModification getEnumModification(String newName) { return (EnumModification) enumDifferences.get(newName); } + private InputObjectModification getInputObjectLocation(String newName) { + if (!inputObjectDifferences.containsKey(newName)) { + inputObjectDifferences.put(newName, new InputObjectModification(newName)); + } + assertTrue(inputObjectDifferences.get(newName) instanceof InputObjectModification); + return (InputObjectModification) inputObjectDifferences.get(newName); + } + private DirectiveModification getDirectiveModification(String newName) { if (!directiveDifferences.containsKey(newName)) { directiveDifferences.put(newName, new DirectiveModification(newName)); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index e2801de8be..6e4ebb3ff2 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -716,11 +716,17 @@ public String getName() { } } + interface InputObjectModificationDetail { + + } + class InputObjectModification implements SchemaModification, InputObjectDifference { private final String oldName; private final String newName; private final boolean nameChanged; + private final List details = new ArrayList<>(); + public InputObjectModification(String oldName, String newName) { this.oldName = oldName; @@ -728,6 +734,12 @@ public InputObjectModification(String oldName, String newName) { this.nameChanged = oldName.equals(newName); } + public InputObjectModification(String newName) { + this.oldName = newName; + this.newName = newName; + this.nameChanged = false; + } + public boolean isNameChanged() { return nameChanged; } @@ -739,6 +751,15 @@ public String getNewName() { public String getOldName() { return oldName; } + + public List getDetails() { + return details; + } + + public List getDetails(Class clazz) { + return (List) FpKit.filterList(details, clazz::isInstance); + } + } //-------Enum @@ -884,7 +905,8 @@ public ScalarModification(String oldName, String newName) { this.newName = newName; this.nameChanged = oldName.equals(newName); } - public ScalarModification( String newName) { + + public ScalarModification(String newName) { this.oldName = newName; this.newName = newName; this.nameChanged = false; @@ -902,6 +924,7 @@ public String getNewName() { public String getOldName() { return oldName; } + public List getDetails() { return details; } @@ -1217,7 +1240,11 @@ class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocati } - class AppliedDirectiveAddition implements ObjectModificationDetail, InterfaceModificationDetail, ScalarModificationDetail, EnumModificationDetail { + class AppliedDirectiveAddition implements ObjectModificationDetail, + InterfaceModificationDetail, + ScalarModificationDetail, + EnumModificationDetail, + InputObjectModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ea86bcc2f8..585c7d247d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1592,6 +1592,35 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "input object added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I @d(arg: "foo") { + a: String + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectLocation).name == "I" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' From d76435bb86507ce6de3c946b2276734ca615fc03 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sun, 6 Nov 2022 08:00:26 +1000 Subject: [PATCH 237/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 24 +++++++++++++++++ .../schema/diffing/ana/SchemaDifference.java | 14 ++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 26 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index b4a6a67f72..fdf76e4459 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -258,6 +258,18 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveEnumLocation location = new AppliedDirectiveEnumLocation(enumVertex.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.ENUM_VALUE)) { + Vertex enumValue = container; + Vertex enumVertex = newSchemaGraph.getEnumForEnumValue(enumValue); + if (isEnumAdded(enumVertex.getName())) { + return; + } + if (isNewEnumValueForExistingEnum(enumVertex.getName(), enumValue.getName())) { + return; + } + AppliedDirectiveEnumValueLocation location = new AppliedDirectiveEnumValueLocation(enumVertex.getName(), enumValue.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveAddition); } else if (container.isOfType(SchemaGraph.INPUT_OBJECT)) { Vertex inputObject = container; if (isInputObjectAdded(inputObject.getName())) { @@ -984,6 +996,18 @@ private boolean isFieldNewForExistingObject(String objectName, String fieldName) return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); } + private boolean isNewEnumValueForExistingEnum(String enumName, String valueName) { + if (!enumDifferences.containsKey(enumName)) { + return false; + } + if (!(enumDifferences.get(enumName) instanceof EnumModification)) { + return false; + } + EnumModification enumModification = (EnumModification) enumDifferences.get(enumName); + List newValues = enumModification.getDetails(EnumValueAddition.class); + return newValues.stream().anyMatch(detail -> detail.getName().equals(valueName)); + } + private boolean isFieldNewForExistingInterface(String interfaceName, String fieldName) { if (!interfaceDifferences.containsKey(interfaceName)) { return false; diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 6e4ebb3ff2..507a3b6923 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1220,7 +1220,21 @@ public String getName() { } class AppliedDirectiveEnumValueLocation implements AppliedDirectiveLocationDetail { + private final String enumName; + private final String valueName; + public AppliedDirectiveEnumValueLocation(String enumName, String valueName) { + this.enumName = enumName; + this.valueName = valueName; + } + + public String getEnumName() { + return enumName; + } + + public String getValueName() { + return valueName; + } } class AppliedDirectiveInputObjectLocation implements AppliedDirectiveLocationDetail { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 585c7d247d..8dc865cd78 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1592,6 +1592,32 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "enum vlaue added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B @d(arg: "foo") } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).enumName == "E" + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).valueName == "B" + appliedDirective[0].name == "d" + } + def "input object added applied directive"() { given: def oldSdl = ''' From 5c0c68ba15bc06f0783f010f73dc16665d79fada Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Sun, 6 Nov 2022 22:36:11 +0100 Subject: [PATCH 238/294] Fixes in TwitterBenchmark (#3006) * Remove unused imports and warnings from TwitterBenchmark * Configure ParserOptions for TwitterBenchmark * Fix javadoc of PersistedQueryCache --- .../persisted/PersistedQueryCache.java | 16 ++--- src/test/java/benchmark/TwitterBenchmark.java | 62 ++++++------------- 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java index 94b7233baf..034e4b4ebb 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java +++ b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCache.java @@ -19,14 +19,14 @@ public interface PersistedQueryCache { * If its present in cache then it must return a PreparsedDocumentEntry where {@link graphql.execution.preparsed.PreparsedDocumentEntry#getDocument()} * is already parsed and validated. This will be passed onto the graphql engine as is. *

- * If its a valid query id but its no present in cache, (cache miss) then you need to call back the "onCacheMiss" function with associated query text. - * This will be compiled and validated by the graphql engine and the a PreparsedDocumentEntry will be passed back ready for you to cache it. + * If it's a valid query id but its no present in cache, (cache miss) then you need to call back the "onCacheMiss" function with associated query text. + * This will be compiled and validated by the graphql engine and the PreparsedDocumentEntry will be passed back ready for you to cache it. *

- * If its not a valid query id then throw a {@link graphql.execution.preparsed.persisted.PersistedQueryNotFound} to indicate this. + * If it's not a valid query id then throw a {@link graphql.execution.preparsed.persisted.PersistedQueryNotFound} to indicate this. * * @param persistedQueryId the persisted query id * @param executionInput the original execution input - * @param onCacheMiss the call back should it be a valid query id but its not currently not in the cache + * @param onCacheMiss the call back should it be a valid query id but it's not currently in the cache * @return a parsed and validated PreparsedDocumentEntry where {@link graphql.execution.preparsed.PreparsedDocumentEntry#getDocument()} is set * @throws graphql.execution.preparsed.persisted.PersistedQueryNotFound if the query id is not know at all and you have no query text * @@ -42,14 +42,14 @@ public interface PersistedQueryCache { * If its present in cache then it must return a PreparsedDocumentEntry where {@link graphql.execution.preparsed.PreparsedDocumentEntry#getDocument()} * is already parsed and validated. This will be passed onto the graphql engine as is. *

- * If its a valid query id but its no present in cache, (cache miss) then you need to call back the "onCacheMiss" function with associated query text. - * This will be compiled and validated by the graphql engine and the a PreparsedDocumentEntry will be passed back ready for you to cache it. + * If it's a valid query id but its no present in cache, (cache miss) then you need to call back the "onCacheMiss" function with associated query text. + * This will be compiled and validated by the graphql engine and the PreparsedDocumentEntry will be passed back ready for you to cache it. *

- * If its not a valid query id then throw a {@link graphql.execution.preparsed.persisted.PersistedQueryNotFound} to indicate this. + * If it's not a valid query id then throw a {@link graphql.execution.preparsed.persisted.PersistedQueryNotFound} to indicate this. * * @param persistedQueryId the persisted query id * @param executionInput the original execution input - * @param onCacheMiss the call back should it be a valid query id but its not currently not in the cache + * @param onCacheMiss the call back should it be a valid query id but it's not currently in the cache * @return a promise to parsed and validated {@link PreparsedDocumentEntry} where {@link graphql.execution.preparsed.PreparsedDocumentEntry#getDocument()} is set * @throws graphql.execution.preparsed.persisted.PersistedQueryNotFound if the query id is not know at all and you have no query text */ diff --git a/src/test/java/benchmark/TwitterBenchmark.java b/src/test/java/benchmark/TwitterBenchmark.java index 21444a9f58..4164f8a40b 100644 --- a/src/test/java/benchmark/TwitterBenchmark.java +++ b/src/test/java/benchmark/TwitterBenchmark.java @@ -1,55 +1,34 @@ package benchmark; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import com.github.javafaker.Code; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; - import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; -import graphql.execution.ExecutionStepInfo; -import graphql.execution.instrumentation.tracing.TracingInstrumentation; -import graphql.execution.preparsed.PreparsedDocumentEntry; -import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.execution.preparsed.persisted.InMemoryPersistedQueryCache; import graphql.execution.preparsed.persisted.PersistedQueryCache; import graphql.execution.preparsed.persisted.PersistedQuerySupport; +import graphql.parser.ParserOptions; import graphql.schema.DataFetcher; -import graphql.schema.DataFetcherFactory; -import graphql.schema.DataFetcherFactoryEnvironment; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.FieldCoordinates; import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLTypeReference; -import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaGeneratorHelper; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; import static graphql.Scalars.GraphQLString; -import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; @Warmup(iterations = 8, time = 10) @Measurement(iterations = 25, time = 10) @@ -94,6 +73,8 @@ public static String mkQuery() { } private static GraphQL buildGraphQL() { + ParserOptions.setDefaultOperationParserOptions(ParserOptions.newParserOptions().maxTokens(100_000).build()); + List leafFields = new ArrayList<>(BREADTH); for (int i = 1; i <= BREADTH; i++) { leafFields.add( @@ -116,12 +97,7 @@ private static GraphQL buildGraphQL() { DataFetcher simpleFetcher = env -> env.getField().getName(); GraphQLCodeRegistry codeReg = GraphQLCodeRegistry.newCodeRegistry() .defaultDataFetcher( - new DataFetcherFactory() { - @Override - public DataFetcher get(DataFetcherFactoryEnvironment environment) { - return simpleFetcher; - } - } + environment -> simpleFetcher ) .build(); @@ -156,6 +132,6 @@ protected Optional getPersistedQueryId(ExecutionInput executionInput) { public static void main(String[] args) { ExecutionResult result = execute(); - int i = 0; + System.out.println(result); } } From 8e35695afb75de735ca54a745b31521efb37dbb6 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 01:03:49 +1000 Subject: [PATCH 239/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 8 +++++ .../schema/diffing/ana/SchemaDifference.java | 6 ++-- .../ana/EditOperationAnalyzerTest.groovy | 32 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index fdf76e4459..27f03334f9 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -278,6 +278,14 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveInputObjectLocation location = new AppliedDirectiveInputObjectLocation(inputObject.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getInputObjectLocation(inputObject.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.UNION)) { + Vertex union = container; + if (isUnionAdded(union.getName())) { + return; + } + AppliedDirectiveUnionLocation location = new AppliedDirectiveUnionLocation(union.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getUnionModification(union.getName()).getDetails().add(appliedDirectiveAddition); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 507a3b6923..9872afb00d 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1254,11 +1254,13 @@ class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocati } - class AppliedDirectiveAddition implements ObjectModificationDetail, + class AppliedDirectiveAddition implements + ObjectModificationDetail, InterfaceModificationDetail, ScalarModificationDetail, EnumModificationDetail, - InputObjectModificationDetail { + InputObjectModificationDetail, + UnionModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 8dc865cd78..4f308829f1 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1,5 +1,6 @@ package graphql.schema.diffing.ana +import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Union import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification @@ -1542,6 +1543,35 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "union added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U = A | B + type A { a: String } + type B { b: String } + ''' + def newSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U @d(arg: "foo") = A | B + type A { a: String } + type B { b: String } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.unionDifferences["U"] instanceof UnionModification + def appliedDirective = (changes.unionDifferences["U"] as UnionModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveUnionLocation).name == "U" + appliedDirective[0].name == "d" + } + def "scalar added applied directive"() { given: def oldSdl = ''' @@ -1592,7 +1622,7 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } - def "enum vlaue added applied directive"() { + def "enum value added applied directive"() { given: def oldSdl = ''' directive @d(arg:String) on ENUM_VALUE From 635832b06bc88f2dcc9ecfa6a83472520662be54 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 01:42:05 +1000 Subject: [PATCH 240/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 44 +++++++++++++- .../schema/diffing/ana/SchemaDifference.java | 26 +++++++++ .../ana/EditOperationAnalyzerTest.groovy | 58 +++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 27f03334f9..4ae69cdcaa 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -127,6 +127,8 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio fieldAdded(editOperation); } else if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { argumentAdded(editOperation); + } else if (editOperation.getTargetVertex().isOfType(SchemaGraph.INPUT_FIELD)) { + inputFieldAdded(editOperation); } break; case DELETE_VERTEX: @@ -277,7 +279,19 @@ private void appliedDirectiveAdded(EditOperation editOperation) { } AppliedDirectiveInputObjectLocation location = new AppliedDirectiveInputObjectLocation(inputObject.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getInputObjectLocation(inputObject.getName()).getDetails().add(appliedDirectiveAddition); + getInputObjectModification(inputObject.getName()).getDetails().add(appliedDirectiveAddition); + } else if (container.isOfType(SchemaGraph.INPUT_FIELD)) { + Vertex inputField = container; + Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); + if (isInputObjectAdded(inputObject.getName())) { + return; + } + if (isNewInputFieldExistingInputObject(inputObject.getName(), inputField.getName())) { + return; + } + AppliedDirectiveInputObjectFieldLocation location = new AppliedDirectiveInputObjectFieldLocation(inputObject.getName(), inputField.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getInputObjectModification(inputObject.getName()).getDetails().add(appliedDirectiveAddition); } else if (container.isOfType(SchemaGraph.UNION)) { Vertex union = container; if (isUnionAdded(union.getName())) { @@ -471,6 +485,16 @@ private void fieldChanged(EditOperation editOperation) { } } + private void inputFieldAdded(EditOperation editOperation) { + Vertex inputField = editOperation.getTargetVertex(); + Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); + if (isInputObjectAdded(inputObject.getName())) { + return; + } + InputObjectModification modification = getInputObjectModification(inputObject.getName()); + modification.getDetails().add(new InputObjectFieldAddition(inputField.getName())); + } + private void fieldAdded(EditOperation editOperation) { Vertex field = editOperation.getTargetVertex(); Vertex fieldsContainerForField = newSchemaGraph.getFieldsContainerForField(field); @@ -942,6 +966,22 @@ private boolean isInputObjectAdded(String name) { return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectAddition; } + private boolean isInputFieldAdded(String name) { + return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectAddition; + } + + private boolean isNewInputFieldExistingInputObject(String inputObjectName, String fieldName) { + if (!inputObjectDifferences.containsKey(inputObjectName)) { + return false; + } + if (!(inputObjectDifferences.get(inputObjectName) instanceof InputObjectModification)) { + return false; + } + InputObjectModification modification = (InputObjectModification) inputObjectDifferences.get(inputObjectName); + List newFields = modification.getDetails(InputObjectFieldAddition.class); + return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } + private boolean isArgumentNewForExistingDirective(String directiveName, String argumentName) { if (!directiveDifferences.containsKey(directiveName)) { return false; @@ -1068,7 +1108,7 @@ private EnumModification getEnumModification(String newName) { return (EnumModification) enumDifferences.get(newName); } - private InputObjectModification getInputObjectLocation(String newName) { + private InputObjectModification getInputObjectModification(String newName) { if (!inputObjectDifferences.containsKey(newName)) { inputObjectDifferences.put(newName, new InputObjectModification(newName)); } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 9872afb00d..ee0c2ec1cd 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -720,6 +720,18 @@ interface InputObjectModificationDetail { } + class InputObjectFieldAddition implements InputObjectModificationDetail { + private final String name; + + public InputObjectFieldAddition(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + class InputObjectModification implements SchemaModification, InputObjectDifference { private final String oldName; private final String newName; @@ -1251,7 +1263,21 @@ public String getName() { class AppliedDirectiveInputObjectFieldLocation implements AppliedDirectiveLocationDetail { + private final String inputObjectName; + private final String fieldName; + public AppliedDirectiveInputObjectFieldLocation(String inputObjectName, String fieldName) { + this.inputObjectName = inputObjectName; + this.fieldName = fieldName; + } + + public String getInputObjectName() { + return inputObjectName; + } + + public String getFieldName() { + return fieldName; + } } class AppliedDirectiveAddition implements diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 4f308829f1..f7bcbffa9e 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1228,6 +1228,34 @@ class EditOperationAnalyzerTest extends Specification { (changes.inputObjectDifferences["I"] as InputObjectAddition).getName() == "I" } + def "input object field added"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + newField: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def fieldAddition = modification.getDetails(InputObjectFieldAddition)[0] + fieldAddition.name == "newField" + } + def "input object deleted"() { given: def oldSdl = ''' @@ -1677,6 +1705,36 @@ class EditOperationAnalyzerTest extends Specification { appliedDirective[0].name == "d" } + def "input object field added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String @d(arg: "foo") + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).fieldName == "a" + appliedDirective[0].name == "d" + } + def "object field added applied directive"() { given: def oldSdl = ''' From 9f0674907526ab6d4d07046f02b6a53ee1e6947e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 01:58:30 +1000 Subject: [PATCH 241/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 15 ++++++++++ .../schema/diffing/ana/SchemaDifference.java | 11 ++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 4ae69cdcaa..f5122648df 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -136,6 +136,8 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio argumentDeleted(editOperation); } else if (editOperation.getSourceVertex().isOfType(SchemaGraph.FIELD)) { fieldDeleted(editOperation); + } else if (editOperation.getSourceVertex().isOfType(SchemaGraph.INPUT_FIELD)) { + inputFieldDeleted(editOperation); } } } @@ -518,6 +520,15 @@ private void fieldAdded(EditOperation editOperation) { } } + private void inputFieldDeleted(EditOperation editOperation) { + Vertex inputField = editOperation.getSourceVertex(); + Vertex inputObject = oldSchemaGraph.getInputObjectForInputField(inputField); + if (isInputObjectDeleted(inputObject.getName())) { + return; + } + getInputObjectModification(inputObject.getName()).getDetails().add(new InputObjectFieldDeletion(inputField.getName())); + } + private void fieldDeleted(EditOperation editOperation) { Vertex deletedField = editOperation.getSourceVertex(); Vertex fieldsContainerForField = oldSchemaGraph.getFieldsContainerForField(deletedField); @@ -966,6 +977,10 @@ private boolean isInputObjectAdded(String name) { return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectAddition; } + private boolean isInputObjectDeleted(String name) { + return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectDeletion; + } + private boolean isInputFieldAdded(String name) { return inputObjectDifferences.containsKey(name) && inputObjectDifferences.get(name) instanceof InputObjectAddition; } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index ee0c2ec1cd..972bdafdb2 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -720,6 +720,17 @@ interface InputObjectModificationDetail { } + class InputObjectFieldDeletion implements InputObjectModificationDetail { + private final String name; + + public InputObjectFieldDeletion(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } class InputObjectFieldAddition implements InputObjectModificationDetail { private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index f7bcbffa9e..5c6048c02b 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1256,6 +1256,34 @@ class EditOperationAnalyzerTest extends Specification { fieldAddition.name == "newField" } + def "input object field deletion"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + toDelete: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def fieldDeletion = modification.getDetails(InputObjectFieldDeletion)[0] + fieldDeletion.name == "toDelete" + } + def "input object deleted"() { given: def oldSdl = ''' From c74c067ddb95fa80b2e49594387a5e4e542147b1 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 02:01:28 +1000 Subject: [PATCH 242/294] work on higher level schema diff --- .../schema/diffing/ana/EditOperationAnalyzer.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index f5122648df..f053ed3e18 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -120,6 +120,8 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio case CHANGE_VERTEX: if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldChanged(editOperation); + }else if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { + handleArgumentChange(editOperation); } break; case INSERT_VERTEX: @@ -145,7 +147,6 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio handleImplementsChanges(editOperations, mapping); handleUnionMemberChanges(editOperations, mapping); handleEnumValuesChanges(editOperations, mapping); - handleArgumentChanges(editOperations, mapping); handleAppliedDirectives(editOperations, mapping); return new EditOperationAnalysisResult( @@ -361,17 +362,6 @@ private void handleEnumValuesChanges(List editOperations, Mapping } } - private void handleArgumentChanges(List editOperations, Mapping mapping) { - for (EditOperation editOperation : editOperations) { - switch (editOperation.getOperation()) { - case CHANGE_VERTEX: - if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { - handleArgumentChange(editOperation); - } - } - } - } - private void handleArgumentChange(EditOperation editOperation) { Vertex argument = editOperation.getTargetVertex(); Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); From 2f456ceaa5a4a18638ba5b5ac44d4520903c31b3 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 02:14:58 +1000 Subject: [PATCH 243/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 11 +++++++- .../schema/diffing/ana/SchemaDifference.java | 19 +++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index f053ed3e18..ce0bffd755 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -120,8 +120,10 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio case CHANGE_VERTEX: if (editOperation.getTargetVertex().isOfType(SchemaGraph.FIELD)) { fieldChanged(editOperation); - }else if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { + } else if (editOperation.getTargetVertex().isOfType(SchemaGraph.ARGUMENT)) { handleArgumentChange(editOperation); + } else if (editOperation.getTargetVertex().isOfType(SchemaGraph.INPUT_FIELD)) { + handleInputFieldChange(editOperation); } break; case INSERT_VERTEX: @@ -362,6 +364,13 @@ private void handleEnumValuesChanges(List editOperations, Mapping } } + private void handleInputFieldChange(EditOperation editOperation) { + Vertex inputField = editOperation.getTargetVertex(); + Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); + String oldName = editOperation.getSourceVertex().getName(); + getInputObjectModification(inputObject.getName()).getDetails().add(new InputObjectFieldRename(oldName, inputField.getName())); + } + private void handleArgumentChange(EditOperation editOperation) { Vertex argument = editOperation.getTargetVertex(); Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 972bdafdb2..41addb5acf 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -731,6 +731,25 @@ public String getName() { return name; } } + + class InputObjectFieldRename implements InputObjectModificationDetail { + private final String oldName; + private final String newName; + + public InputObjectFieldRename(String oldName, String newName) { + this.oldName = oldName; + this.newName = newName; + } + + public String getOldName() { + return oldName; + } + + public String getNewName() { + return newName; + } + } + class InputObjectFieldAddition implements InputObjectModificationDetail { private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 5c6048c02b..0c9c44c1de 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1284,6 +1284,34 @@ class EditOperationAnalyzerTest extends Specification { fieldDeletion.name == "toDelete" } + def "input object field renamed"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + barNew: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def fieldDeletion = modification.getDetails(InputObjectFieldRename)[0] + fieldDeletion.oldName == "bar" + fieldDeletion.newName == "barNew" + } + def "input object deleted"() { given: def oldSdl = ''' From 26d0ad6731af169c873e77c4d98ada04595de8ad Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 02:35:18 +1000 Subject: [PATCH 244/294] work on higher level schema diff --- .../schema/diffing/SchemaGraphFactory.java | 4 +-- .../diffing/ana/EditOperationAnalyzer.java | 20 +++++++++++++ .../schema/diffing/ana/SchemaDifference.java | 24 ++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java index 30f0709ea2..37ac8d5e31 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraphFactory.java @@ -159,9 +159,9 @@ private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField i GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type); Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName())); Edge typeEdge = new Edge(inputFieldVertex, typeVertex); - String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type); + String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type) + ";defaultValue="; if (inputField.hasSetDefaultValue()) { - typeEdgeLabel += ";defaultValue=" + AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType(), GraphQLContext.getDefault(), Locale.getDefault())); + typeEdgeLabel += AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType(), GraphQLContext.getDefault(), Locale.getDefault())); } typeEdge.setLabel(typeEdgeLabel); diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ce0bffd755..add5a281fb 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -368,6 +368,7 @@ private void handleInputFieldChange(EditOperation editOperation) { Vertex inputField = editOperation.getTargetVertex(); Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); String oldName = editOperation.getSourceVertex().getName(); + String newName = inputObject.getName(); getInputObjectModification(inputObject.getName()).getDetails().add(new InputObjectFieldRename(oldName, inputField.getName())); } @@ -793,6 +794,25 @@ private void typeEdgeChanged(EditOperation editOperation) { fieldTypeChanged(editOperation); } else if (from.isOfType(SchemaGraph.ARGUMENT)) { argumentTypeOrDefaultValueChanged(editOperation); + } else if (from.isOfType(SchemaGraph.INPUT_FIELD)) { + inputFieldTypeOrDefaultValueChanged(editOperation); + } + } + + private void inputFieldTypeOrDefaultValueChanged(EditOperation editOperation) { + Edge targetEdge = editOperation.getTargetEdge(); + Vertex inputField = targetEdge.getFrom(); + Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); + String oldDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getSourceEdge()); + String newDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getTargetEdge()); + if (!oldDefaultValue.equals(newDefaultValue)) { + + } + String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + if (!oldType.equals(newType)) { + InputObjectFieldTypeModification inputObjectFieldTypeModification = new InputObjectFieldTypeModification(inputField.getName(), oldType, newType); + getInputObjectModification(inputObject.getName()).getDetails().add(inputObjectFieldTypeModification); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 41addb5acf..302db8c444 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -750,6 +750,30 @@ public String getNewName() { } } + class InputObjectFieldTypeModification implements InputObjectModificationDetail { + private final String fieldName; + private final String oldType; + private final String newType; + + public InputObjectFieldTypeModification(String fieldName, String oldType, String newType) { + this.fieldName = fieldName; + this.oldType = oldType; + this.newType = newType; + } + + public String getFieldName() { + return fieldName; + } + + public String getOldType() { + return oldType; + } + + public String getNewType() { + return newType; + } + } + class InputObjectFieldAddition implements InputObjectModificationDetail { private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 0c9c44c1de..c48e015d66 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1312,6 +1312,34 @@ class EditOperationAnalyzerTest extends Specification { fieldDeletion.newName == "barNew" } + def "input object wrapping type changed"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: [String] + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def typeModification = modification.getDetails(InputObjectFieldTypeModification)[0] + typeModification.oldType == "String" + typeModification.newType == "[String]" + } + def "input object deleted"() { given: def oldSdl = ''' From 75ad028692000839ff604cbb784462fba566ca93 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 8 Nov 2022 02:42:18 +1000 Subject: [PATCH 245/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 18 +++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 30 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index add5a281fb..ed801df312 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -657,10 +657,28 @@ private void typeEdgeInserted(EditOperation editOperation, List e typeEdgeInsertedForField(editOperation, editOperations, mapping); } else if (from.isOfType(SchemaGraph.ARGUMENT)) { typeEdgeInsertedForArgument(editOperation, editOperations, mapping); + } else if (from.isOfType(SchemaGraph.INPUT_FIELD)) { + typeEdgeInsertedForInputField(editOperation, editOperations, mapping); } } + private void typeEdgeInsertedForInputField(EditOperation editOperation, List editOperations, Mapping mapping) { + Vertex inputField = editOperation.getTargetEdge().getFrom(); + Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); + if (isInputObjectAdded(inputObject.getName())) { + return; + } + if (isNewInputFieldExistingInputObject(inputObject.getName(), inputField.getName())) { + return; + } + String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); + EditOperation deletedTypeEdgeOperation = findDeletedEdge(inputField, editOperations, mapping); + String oldType = getTypeFromEdgeLabel(deletedTypeEdgeOperation.getSourceEdge()); + InputObjectFieldTypeModification inputObjectFieldTypeModification = new InputObjectFieldTypeModification(inputField.getName(), oldType, newType); + getInputObjectModification(inputObject.getName()).getDetails().add(inputObjectFieldTypeModification); + } + private void typeEdgeInsertedForArgument(EditOperation editOperation, List editOperations, Mapping mapping) { Vertex argument = editOperation.getTargetEdge().getFrom(); Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index c48e015d66..c7556ec21d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1312,7 +1312,7 @@ class EditOperationAnalyzerTest extends Specification { fieldDeletion.newName == "barNew" } - def "input object wrapping type changed"() { + def "input object field wrapping type changed"() { given: def oldSdl = ''' type Query { @@ -1340,6 +1340,34 @@ class EditOperationAnalyzerTest extends Specification { typeModification.newType == "[String]" } + def "input object field type changed"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: ID + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def typeModification = modification.getDetails(InputObjectFieldTypeModification)[0] + typeModification.oldType == "String" + typeModification.newType == "ID" + } + def "input object deleted"() { given: def oldSdl = ''' From e4caa34ea615288a8b811e46804a4b56f19a8302 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 09:04:24 +1000 Subject: [PATCH 246/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 3 +- .../schema/diffing/ana/SchemaDifference.java | 23 +++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 28 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ed801df312..ef9888d669 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -824,7 +824,8 @@ private void inputFieldTypeOrDefaultValueChanged(EditOperation editOperation) { String oldDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getSourceEdge()); String newDefaultValue = getDefaultValueFromEdgeLabel(editOperation.getTargetEdge()); if (!oldDefaultValue.equals(newDefaultValue)) { - + InputObjectFieldDefaultValueModification modification = new InputObjectFieldDefaultValueModification(inputField.getName(), oldDefaultValue, newDefaultValue); + getInputObjectModification(inputObject.getName()).getDetails().add(modification); } String oldType = getTypeFromEdgeLabel(editOperation.getSourceEdge()); String newType = getTypeFromEdgeLabel(editOperation.getTargetEdge()); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 302db8c444..dba44ce64c 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -750,6 +750,29 @@ public String getNewName() { } } + class InputObjectFieldDefaultValueModification implements InputObjectModificationDetail { + private final String fieldName; + private final String oldDefaultValue; + private final String newDefaultValue; + + public InputObjectFieldDefaultValueModification(String fieldName, String oldDefaultValue, String newDefaultValue) { + this.fieldName = fieldName; + this.oldDefaultValue = oldDefaultValue; + this.newDefaultValue = newDefaultValue; + } + + public String getFieldName() { + return fieldName; + } + + public String getOldDefaultValue() { + return oldDefaultValue; + } + + public String getNewDefaultValue() { + return newDefaultValue; + } + } class InputObjectFieldTypeModification implements InputObjectModificationDetail { private final String fieldName; private final String oldType; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index c7556ec21d..c655c81e7d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1368,6 +1368,34 @@ class EditOperationAnalyzerTest extends Specification { typeModification.newType == "ID" } + def "input object field default value changed"() { + given: + def oldSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String = "A" + } + ''' + def newSdl = ''' + type Query { + foo(arg: I): String + } + input I { + bar: String = "B" + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def modification = changes.inputObjectDifferences["I"] as InputObjectModification + def modificationDetail = modification.getDetails(InputObjectFieldDefaultValueModification)[0] + modificationDetail.oldDefaultValue == '"A"' + modificationDetail.newDefaultValue == '"B"' + } + def "input object deleted"() { given: def oldSdl = ''' From dd4ac623d41c4a1aad3b29deccf658489b022983 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 09:48:51 +1000 Subject: [PATCH 247/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 27 +++++++++++++++++-- .../schema/diffing/ana/SchemaDifference.java | 18 ++++++++++++- .../ana/EditOperationAnalyzerTest.groovy | 27 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ef9888d669..810460bfe9 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -161,6 +161,7 @@ public EditOperationAnalysisResult analyzeEdits(List editOperatio directiveDifferences); } + private void handleAppliedDirectives(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { @@ -175,7 +176,29 @@ private void handleAppliedDirectives(List editOperations, Mapping appliedDirectiveArgumentChanged(editOperation); } break; + case DELETE_VERTEX: + if (editOperation.getSourceVertex().isOfType(SchemaGraph.APPLIED_ARGUMENT)) { + appliedDirectiveArgumentDeleted(editOperation); + } + break; + + } + } + + } + + private void appliedDirectiveArgumentDeleted(EditOperation editOperation) { + Vertex deletedArgument = editOperation.getSourceVertex(); + Vertex appliedDirective = oldSchemaGraph.getAppliedDirectiveForAppliedArgument(deletedArgument); + Vertex container = oldSchemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + if (container.isOfType(SchemaGraph.FIELD)) { + Vertex field = container; + Vertex interfaceOrObjective = oldSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); + getObjectModification(object.getName()).getDetails().add(new AppliedDirectiveArgumentDeletion(location, deletedArgument.getName())); } } @@ -809,7 +832,7 @@ private void typeEdgeChanged(EditOperation editOperation) { Vertex from = targetEdge.getFrom(); Vertex to = targetEdge.getTo(); if (from.isOfType(SchemaGraph.FIELD)) { - fieldTypeChanged(editOperation); + outputFieldTypeChanged(editOperation); } else if (from.isOfType(SchemaGraph.ARGUMENT)) { argumentTypeOrDefaultValueChanged(editOperation); } else if (from.isOfType(SchemaGraph.INPUT_FIELD)) { @@ -898,7 +921,7 @@ private void argumentTypeOrDefaultValueChanged(EditOperation editOperation) { } - private void fieldTypeChanged(EditOperation editOperation) { + private void outputFieldTypeChanged(EditOperation editOperation) { Edge targetEdge = editOperation.getTargetEdge(); Vertex field = targetEdge.getFrom(); Vertex container = newSchemaGraph.getFieldsContainerForField(field); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index dba44ce64c..0cdf82dda7 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -773,6 +773,7 @@ public String getNewDefaultValue() { return newDefaultValue; } } + class InputObjectFieldTypeModification implements InputObjectModificationDetail { private final String fieldName; private final String oldType; @@ -1393,10 +1394,25 @@ class AppliedDirectiveArgumentAddition { } - class AppliedDirectiveArgumentDeletion { + class AppliedDirectiveArgumentDeletion implements ObjectModificationDetail { + private final AppliedDirectiveLocationDetail locationDetail; + private final String argumentName; + + public AppliedDirectiveArgumentDeletion(AppliedDirectiveLocationDetail locationDetail, String argumentName) { + this.locationDetail = locationDetail; + this.argumentName = argumentName; + } + public AppliedDirectiveLocationDetail getLocationDetail() { + return locationDetail; + } + + public String getArgumentName() { + return argumentName; + } } + class AppliedDirectiveArgumentValueModification implements ObjectModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String argumentName; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index c655c81e7d..ff2abb794b 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1957,6 +1957,33 @@ class EditOperationAnalyzerTest extends Specification { argumentRenames[0].newName == "arg2" } + def "object field applied directive argument deleted"() { + given: + def oldSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1: String) on FIELD_DEFINITION + + type Query { + foo: String @d + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentDeletions = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as AppliedDirectiveObjectFieldLocation + location.objectName == "Query" + location.fieldName == "foo" + argumentDeletions[0].argumentName == "arg1" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From d846a579572a3ada20eb51c1013dc5f7a62d4cb8 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 09:57:17 +1000 Subject: [PATCH 248/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 5 +++ .../schema/diffing/ana/SchemaDifference.java | 10 +++++- .../ana/EditOperationAnalyzerTest.groovy | 33 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 810460bfe9..ff79b65290 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -199,6 +199,11 @@ private void appliedDirectiveArgumentDeleted(EditOperation editOperation) { Vertex object = interfaceOrObjective; AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); getObjectModification(object.getName()).getDetails().add(new AppliedDirectiveArgumentDeletion(location, deletedArgument.getName())); + }else { + assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = interfaceOrObjective; + AppliedDirectiveInterfaceFieldLocation location = new AppliedDirectiveInterfaceFieldLocation(interfaze.getName(), field.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(new AppliedDirectiveArgumentDeletion(location, deletedArgument.getName())); } } diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 0cdf82dda7..717afe6242 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1239,6 +1239,14 @@ public AppliedDirectiveInterfaceFieldLocation(String interfaceName, String field this.interfaceName = interfaceName; this.fieldName = fieldName; } + + public String getFieldName() { + return fieldName; + } + + public String getInterfaceName() { + return interfaceName; + } } class AppliedDirectiveScalarLocation implements AppliedDirectiveLocationDetail { @@ -1394,7 +1402,7 @@ class AppliedDirectiveArgumentAddition { } - class AppliedDirectiveArgumentDeletion implements ObjectModificationDetail { + class AppliedDirectiveArgumentDeletion implements ObjectModificationDetail, InterfaceModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String argumentName; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index ff2abb794b..27cc17828d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1984,6 +1984,39 @@ class EditOperationAnalyzerTest extends Specification { argumentDeletions[0].argumentName == "arg1" } + def "interface field applied directive argument deleted"() { + given: + def oldSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query implements I{ + foo: String + } + interface I { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query implements I{ + foo: String + } + interface I { + foo: String @d + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def argumentDeletions = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as AppliedDirectiveInterfaceFieldLocation + location.interfaceName == "I" + location.fieldName == "foo" + argumentDeletions[0].argumentName == "arg1" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From aa5afc688435c9de920316ace4d55decdad0dd5f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 16:44:28 +1000 Subject: [PATCH 249/294] work on higher level schema diff --- ...rationAnalyzerAppliedDirectivesTest.groovy | 386 ++++++++++++++++++ .../ana/EditOperationAnalyzerTest.groovy | 359 ---------------- 2 files changed, 386 insertions(+), 359 deletions(-) create mode 100644 src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy new file mode 100644 index 0000000000..ef43589024 --- /dev/null +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -0,0 +1,386 @@ +package graphql.schema.diffing.ana + +import graphql.TestUtil +import graphql.schema.diffing.SchemaDiffing +import spock.lang.Specification + +class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ + + def "interface field applied directive argument deleted"() { + given: + def oldSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query implements I{ + foo: String + } + interface I { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query implements I{ + foo: String + } + interface I { + foo: String @d + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof SchemaDifference.InterfaceModification + def argumentDeletions = (changes.interfaceDifferences["I"] as SchemaDifference.InterfaceModification).getDetails(SchemaDifference.AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as SchemaDifference.AppliedDirectiveInterfaceFieldLocation + location.interfaceName == "I" + location.fieldName == "foo" + argumentDeletions[0].argumentName == "arg1" + } + + def "input object field added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String @d(arg: "foo") + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof SchemaDifference.InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as SchemaDifference.InputObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectFieldLocation).fieldName == "a" + appliedDirective[0].name == "d" + } + + def "object field added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).objectName == "Query" + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).fieldName == "foo" + appliedDirective[0].name == "d" + } + + def "object field applied directive argument value changed"() { + given: + def oldSdl = ''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo1") + } + ''' + def newSdl = ''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo2") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification + def argumentValueModifications = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentValueModification) + (argumentValueModifications[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).objectName == "Query" + (argumentValueModifications[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).fieldName == "foo" + argumentValueModifications[0].argumentName == "arg" + argumentValueModifications[0].oldValue == '"foo1"' + argumentValueModifications[0].newValue == '"foo2"' + } + + def "object field applied directive argument name changed"() { + given: + def oldSdl = ''' + directive @d(arg1:String, arg2: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1: String, arg2: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg2: "foo") + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification + def argumentRenames = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentRename) + def location = argumentRenames[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation + location.objectName == "Query" + location.fieldName == "foo" + argumentRenames[0].oldName == "arg1" + argumentRenames[0].newName == "arg2" + } + + def "object field applied directive argument deleted"() { + given: + def oldSdl = ''' + directive @d(arg1:String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg1: "foo") + } + ''' + def newSdl = ''' + directive @d(arg1: String) on FIELD_DEFINITION + + type Query { + foo: String @d + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification + def argumentDeletions = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation + location.objectName == "Query" + location.fieldName == "foo" + argumentDeletions[0].argumentName == "arg1" + } + + def "input object added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I @d(arg: "foo") { + a: String + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof SchemaDifference.InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as SchemaDifference.InputObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectLocation).name == "I" + appliedDirective[0].name == "d" + } + + def "object added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on OBJECT + + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on OBJECT + + type Query @d(arg: "foo") { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectLocation).name == "Query" + appliedDirective[0].name == "d" + } + + def "interface added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INTERFACE + + type Query implements I{ + foo: String + } + interface I { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg: String) on INTERFACE + + type Query implements I { + foo: String + } + interface I @d(arg: "foo") { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof SchemaDifference.InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as SchemaDifference.InterfaceModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInterfaceLocation).name == "I" + appliedDirective[0].name == "d" + } + + def "union added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U = A | B + type A { a: String } + type B { b: String } + ''' + def newSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U @d(arg: "foo") = A | B + type A { a: String } + type B { b: String } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.unionDifferences["U"] instanceof SchemaDifference.UnionModification + def appliedDirective = (changes.unionDifferences["U"] as SchemaDifference.UnionModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveUnionLocation).name == "U" + appliedDirective[0].name == "d" + } + + def "scalar added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime + type Query { + foo: DateTime + } + ''' + def newSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime @d(arg: "foo") + type Query { + foo: DateTime + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.scalarDifferences["DateTime"] instanceof SchemaDifference.ScalarModification + def appliedDirective = (changes.scalarDifferences["DateTime"] as SchemaDifference.ScalarModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveScalarLocation).name == "DateTime" + appliedDirective[0].name == "d" + } + + def "enum added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM + enum E { A, B } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM + enum E @d(arg: "foo") { A, B } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof SchemaDifference.EnumModification + def appliedDirective = (changes.enumDifferences["E"] as SchemaDifference.EnumModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumLocation).name == "E" + appliedDirective[0].name == "d" + } + + def "enum value added applied directive"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B @d(arg: "foo") } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof SchemaDifference.EnumModification + def appliedDirective = (changes.enumDifferences["E"] as SchemaDifference.EnumModification).getDetails(SchemaDifference.AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumValueLocation).enumName == "E" + (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumValueLocation).valueName == "B" + appliedDirective[0].name == "d" + } + + + + + + + EditOperationAnalysisResult calcDiff( + String oldSdl, + String newSdl + ) { + def oldSchema = TestUtil.schema(oldSdl) + def newSchema = TestUtil.schema(newSdl) + def changes = new SchemaDiffing().diffAndAnalyze(oldSchema, newSchema) + return changes + } + +} diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy index 27cc17828d..a60a747a3d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerTest.groovy @@ -1655,367 +1655,8 @@ class EditOperationAnalyzerTest extends Specification { argTypeModification[0].newType == '[String]!' } - def "object added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on OBJECT - - type Query { - foo: String - } - ''' - def newSdl = ''' - directive @d(arg: String) on OBJECT - - type Query @d(arg: "foo") { - foo: String - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.objectDifferences["Query"] instanceof ObjectModification - def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveObjectLocation).name == "Query" - appliedDirective[0].name == "d" - } - def "interface added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on INTERFACE - - type Query implements I{ - foo: String - } - interface I { - foo: String - } - ''' - def newSdl = ''' - directive @d(arg: String) on INTERFACE - - type Query implements I { - foo: String - } - interface I @d(arg: "foo") { - foo: String - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.interfaceDifferences["I"] instanceof InterfaceModification - def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceLocation).name == "I" - appliedDirective[0].name == "d" - } - - def "union added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg: String) on UNION - type Query { - foo: U - } - union U = A | B - type A { a: String } - type B { b: String } - ''' - def newSdl = ''' - directive @d(arg: String) on UNION - type Query { - foo: U - } - union U @d(arg: "foo") = A | B - type A { a: String } - type B { b: String } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.unionDifferences["U"] instanceof UnionModification - def appliedDirective = (changes.unionDifferences["U"] as UnionModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveUnionLocation).name == "U" - appliedDirective[0].name == "d" - } - def "scalar added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on SCALAR - scalar DateTime - type Query { - foo: DateTime - } - ''' - def newSdl = ''' - directive @d(arg:String) on SCALAR - scalar DateTime @d(arg: "foo") - type Query { - foo: DateTime - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.scalarDifferences["DateTime"] instanceof ScalarModification - def appliedDirective = (changes.scalarDifferences["DateTime"] as ScalarModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveScalarLocation).name == "DateTime" - appliedDirective[0].name == "d" - } - - def "enum added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on ENUM - enum E { A, B } - type Query { - foo: E - } - ''' - def newSdl = ''' - directive @d(arg:String) on ENUM - enum E @d(arg: "foo") { A, B } - type Query { - foo: E - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.enumDifferences["E"] instanceof EnumModification - def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveEnumLocation).name == "E" - appliedDirective[0].name == "d" - } - - def "enum value added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on ENUM_VALUE - enum E { A, B } - type Query { - foo: E - } - ''' - def newSdl = ''' - directive @d(arg:String) on ENUM_VALUE - enum E { A, B @d(arg: "foo") } - type Query { - foo: E - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.enumDifferences["E"] instanceof EnumModification - def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).enumName == "E" - (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).valueName == "B" - appliedDirective[0].name == "d" - } - - def "input object added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on INPUT_OBJECT - input I { - a: String - } - type Query { - foo(arg: I): String - } - ''' - def newSdl = ''' - directive @d(arg:String) on INPUT_OBJECT - input I @d(arg: "foo") { - a: String - } - type Query { - foo(arg: I): String - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.inputObjectDifferences["I"] instanceof InputObjectModification - def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectLocation).name == "I" - appliedDirective[0].name == "d" - } - - def "input object field added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on INPUT_FIELD_DEFINITION - input I { - a: String - } - type Query { - foo(arg: I): String - } - ''' - def newSdl = ''' - directive @d(arg:String) on INPUT_FIELD_DEFINITION - input I { - a: String @d(arg: "foo") - } - type Query { - foo(arg: I): String - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.inputObjectDifferences["I"] instanceof InputObjectModification - def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" - (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).fieldName == "a" - appliedDirective[0].name == "d" - } - - def "object field added applied directive"() { - given: - def oldSdl = ''' - directive @d(arg:String) on FIELD_DEFINITION - - type Query { - foo: String - } - ''' - def newSdl = ''' - directive @d(arg: String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg: "foo") - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.objectDifferences["Query"] instanceof ObjectModification - def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" - (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" - appliedDirective[0].name == "d" - } - - def "object field applied directive argument value changed"() { - given: - def oldSdl = ''' - directive @d(arg:String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg: "foo1") - } - ''' - def newSdl = ''' - directive @d(arg: String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg: "foo2") - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.objectDifferences["Query"] instanceof ObjectModification - def argumentValueModifications = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentValueModification) - (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" - (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" - argumentValueModifications[0].argumentName == "arg" - argumentValueModifications[0].oldValue == '"foo1"' - argumentValueModifications[0].newValue == '"foo2"' - } - - def "object field applied directive argument name changed"() { - given: - def oldSdl = ''' - directive @d(arg1:String, arg2: String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg1: "foo") - } - ''' - def newSdl = ''' - directive @d(arg1: String, arg2: String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg2: "foo") - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.objectDifferences["Query"] instanceof ObjectModification - def argumentRenames = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentRename) - def location = argumentRenames[0].locationDetail as AppliedDirectiveObjectFieldLocation - location.objectName == "Query" - location.fieldName == "foo" - argumentRenames[0].oldName == "arg1" - argumentRenames[0].newName == "arg2" - } - - def "object field applied directive argument deleted"() { - given: - def oldSdl = ''' - directive @d(arg1:String) on FIELD_DEFINITION - - type Query { - foo: String @d(arg1: "foo") - } - ''' - def newSdl = ''' - directive @d(arg1: String) on FIELD_DEFINITION - - type Query { - foo: String @d - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.objectDifferences["Query"] instanceof ObjectModification - def argumentDeletions = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentDeletion) - def location = argumentDeletions[0].locationDetail as AppliedDirectiveObjectFieldLocation - location.objectName == "Query" - location.fieldName == "foo" - argumentDeletions[0].argumentName == "arg1" - } - - def "interface field applied directive argument deleted"() { - given: - def oldSdl = ''' - directive @d(arg1:String) on FIELD_DEFINITION - - type Query implements I{ - foo: String - } - interface I { - foo: String @d(arg1: "foo") - } - ''' - def newSdl = ''' - directive @d(arg1:String) on FIELD_DEFINITION - - type Query implements I{ - foo: String - } - interface I { - foo: String @d - } - ''' - when: - def changes = calcDiff(oldSdl, newSdl) - then: - changes.interfaceDifferences["I"] instanceof InterfaceModification - def argumentDeletions = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveArgumentDeletion) - def location = argumentDeletions[0].locationDetail as AppliedDirectiveInterfaceFieldLocation - location.interfaceName == "I" - location.fieldName == "foo" - argumentDeletions[0].argumentName == "arg1" - } EditOperationAnalysisResult calcDiff( From 59f80f611b6171b28d570b2119b64034fe218ace Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 17:21:33 +1000 Subject: [PATCH 250/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 20 +++ .../schema/diffing/ana/SchemaDifference.java | 22 ++- ...rationAnalyzerAppliedDirectivesTest.groovy | 138 +++++++++++------- 3 files changed, 123 insertions(+), 57 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ff79b65290..657d513aa3 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -261,6 +261,26 @@ private void appliedDirectiveAdded(EditOperation editOperation) { AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } + }else if(container.isOfType(SchemaGraph.ARGUMENT)) { + Vertex argument = container; + Vertex field = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if(interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + if (isObjectAdded(object.getName())) { + return; + } + if (isFieldNewForExistingObject(object.getName(), field.getName())) { + return; + } + if (isArgumentNewForExistingObjectField(object.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(),argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } + } else if (container.isOfType(SchemaGraph.OBJECT)) { Vertex object = container; if (isObjectAdded(object.getName())) { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 717afe6242..da24e57de6 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1289,8 +1289,28 @@ public String getName() { } } - class AppliedDirectiveArgumentLocation implements AppliedDirectiveLocationDetail { + class AppliedDirectiveObjectFieldArgumentLocation implements AppliedDirectiveLocationDetail { + private final String objectName; + private final String fieldName; + private final String argumentName; + + public AppliedDirectiveObjectFieldArgumentLocation(String objectName, String fieldName, String argumentName) { + this.objectName = objectName; + this.fieldName = fieldName; + this.argumentName = argumentName; + } + public String getObjectName() { + return objectName; + } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } } class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index ef43589024..669c855ed9 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -4,9 +4,11 @@ import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing import spock.lang.Specification +import static graphql.schema.diffing.ana.SchemaDifference.* + class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ - def "interface field applied directive argument deleted"() { + def "applied directive argument deleted interface field "() { given: def oldSdl = ''' directive @d(arg1:String) on FIELD_DEFINITION @@ -31,15 +33,15 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.interfaceDifferences["I"] instanceof SchemaDifference.InterfaceModification - def argumentDeletions = (changes.interfaceDifferences["I"] as SchemaDifference.InterfaceModification).getDetails(SchemaDifference.AppliedDirectiveArgumentDeletion) - def location = argumentDeletions[0].locationDetail as SchemaDifference.AppliedDirectiveInterfaceFieldLocation + changes.interfaceDifferences["I"] instanceof InterfaceModification + def argumentDeletions = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as AppliedDirectiveInterfaceFieldLocation location.interfaceName == "I" location.fieldName == "foo" argumentDeletions[0].argumentName == "arg1" } - def "input object field added applied directive"() { + def "applied directive added input object field "() { given: def oldSdl = ''' directive @d(arg:String) on INPUT_FIELD_DEFINITION @@ -62,14 +64,14 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.inputObjectDifferences["I"] instanceof SchemaDifference.InputObjectModification - def appliedDirective = (changes.inputObjectDifferences["I"] as SchemaDifference.InputObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectFieldLocation).fieldName == "a" + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).fieldName == "a" appliedDirective[0].name == "d" } - def "object field added applied directive"() { + def "applied directive added object field"() { given: def oldSdl = ''' directive @d(arg:String) on FIELD_DEFINITION @@ -88,14 +90,14 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification - def appliedDirective = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).objectName == "Query" - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).fieldName == "foo" + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" appliedDirective[0].name == "d" } - def "object field applied directive argument value changed"() { + def "applied directive argument value changed object field "() { given: def oldSdl = ''' directive @d(arg:String) on FIELD_DEFINITION @@ -114,16 +116,16 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification - def argumentValueModifications = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentValueModification) - (argumentValueModifications[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).objectName == "Query" - (argumentValueModifications[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation).fieldName == "foo" + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentValueModifications = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentValueModification) + (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" + (argumentValueModifications[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" argumentValueModifications[0].argumentName == "arg" argumentValueModifications[0].oldValue == '"foo1"' argumentValueModifications[0].newValue == '"foo2"' } - def "object field applied directive argument name changed"() { + def "applied directive argument name changed object field"() { given: def oldSdl = ''' directive @d(arg1:String, arg2: String) on FIELD_DEFINITION @@ -142,16 +144,16 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification - def argumentRenames = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentRename) - def location = argumentRenames[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentRenames = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentRename) + def location = argumentRenames[0].locationDetail as AppliedDirectiveObjectFieldLocation location.objectName == "Query" location.fieldName == "foo" argumentRenames[0].oldName == "arg1" argumentRenames[0].newName == "arg2" } - def "object field applied directive argument deleted"() { + def "applied directive argument deleted object field"() { given: def oldSdl = ''' directive @d(arg1:String) on FIELD_DEFINITION @@ -170,15 +172,15 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification - def argumentDeletions = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveArgumentDeletion) - def location = argumentDeletions[0].locationDetail as SchemaDifference.AppliedDirectiveObjectFieldLocation + changes.objectDifferences["Query"] instanceof ObjectModification + def argumentDeletions = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveArgumentDeletion) + def location = argumentDeletions[0].locationDetail as AppliedDirectiveObjectFieldLocation location.objectName == "Query" location.fieldName == "foo" argumentDeletions[0].argumentName == "arg1" } - def "input object added applied directive"() { + def "applied directive added input object"() { given: def oldSdl = ''' directive @d(arg:String) on INPUT_OBJECT @@ -201,13 +203,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.inputObjectDifferences["I"] instanceof SchemaDifference.InputObjectModification - def appliedDirective = (changes.inputObjectDifferences["I"] as SchemaDifference.InputObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInputObjectLocation).name == "I" + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectLocation).name == "I" appliedDirective[0].name == "d" } - def "object added applied directive"() { + def "applied directive added object"() { given: def oldSdl = ''' directive @d(arg:String) on OBJECT @@ -226,13 +228,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.objectDifferences["Query"] instanceof SchemaDifference.ObjectModification - def appliedDirective = (changes.objectDifferences["Query"] as SchemaDifference.ObjectModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveObjectLocation).name == "Query" + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectLocation).name == "Query" appliedDirective[0].name == "d" } - def "interface added applied directive"() { + def "applied directive added interface"() { given: def oldSdl = ''' directive @d(arg:String) on INTERFACE @@ -257,13 +259,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.interfaceDifferences["I"] instanceof SchemaDifference.InterfaceModification - def appliedDirective = (changes.interfaceDifferences["I"] as SchemaDifference.InterfaceModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveInterfaceLocation).name == "I" + changes.interfaceDifferences["I"] instanceof InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceLocation).name == "I" appliedDirective[0].name == "d" } - def "union added applied directive"() { + def "applied directive added union"() { given: def oldSdl = ''' directive @d(arg: String) on UNION @@ -286,13 +288,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.unionDifferences["U"] instanceof SchemaDifference.UnionModification - def appliedDirective = (changes.unionDifferences["U"] as SchemaDifference.UnionModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveUnionLocation).name == "U" + changes.unionDifferences["U"] instanceof UnionModification + def appliedDirective = (changes.unionDifferences["U"] as UnionModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveUnionLocation).name == "U" appliedDirective[0].name == "d" } - def "scalar added applied directive"() { + def "applied directive added scalar"() { given: def oldSdl = ''' directive @d(arg:String) on SCALAR @@ -311,13 +313,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.scalarDifferences["DateTime"] instanceof SchemaDifference.ScalarModification - def appliedDirective = (changes.scalarDifferences["DateTime"] as SchemaDifference.ScalarModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveScalarLocation).name == "DateTime" + changes.scalarDifferences["DateTime"] instanceof ScalarModification + def appliedDirective = (changes.scalarDifferences["DateTime"] as ScalarModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveScalarLocation).name == "DateTime" appliedDirective[0].name == "d" } - def "enum added applied directive"() { + def "applied directive added enum"() { given: def oldSdl = ''' directive @d(arg:String) on ENUM @@ -336,13 +338,13 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.enumDifferences["E"] instanceof SchemaDifference.EnumModification - def appliedDirective = (changes.enumDifferences["E"] as SchemaDifference.EnumModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumLocation).name == "E" + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumLocation).name == "E" appliedDirective[0].name == "d" } - def "enum value added applied directive"() { + def "applied directive added enum value"() { given: def oldSdl = ''' directive @d(arg:String) on ENUM_VALUE @@ -361,13 +363,37 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ when: def changes = calcDiff(oldSdl, newSdl) then: - changes.enumDifferences["E"] instanceof SchemaDifference.EnumModification - def appliedDirective = (changes.enumDifferences["E"] as SchemaDifference.EnumModification).getDetails(SchemaDifference.AppliedDirectiveAddition) - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumValueLocation).enumName == "E" - (appliedDirective[0].locationDetail as SchemaDifference.AppliedDirectiveEnumValueLocation).valueName == "B" + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).enumName == "E" + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).valueName == "B" appliedDirective[0].name == "d" } + def "applied directive added object field argument"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query { + foo(arg: String) : String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query { + foo(arg: String @d(arg: "foo")) : String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).objectName == "Query" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).fieldName == "foo" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } From 2e9f18c215c869264fd731e02ebada2e35f5cd5a Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Fri, 11 Nov 2022 18:33:38 +1000 Subject: [PATCH 251/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 93 ++++++++++++------- .../schema/diffing/ana/SchemaDifference.java | 24 +++++ ...rationAnalyzerAppliedDirectivesTest.groovy | 33 +++++++ 3 files changed, 115 insertions(+), 35 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 657d513aa3..c5c5335f80 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -199,7 +199,7 @@ private void appliedDirectiveArgumentDeleted(EditOperation editOperation) { Vertex object = interfaceOrObjective; AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); getObjectModification(object.getName()).getDetails().add(new AppliedDirectiveArgumentDeletion(location, deletedArgument.getName())); - }else { + } else { assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = interfaceOrObjective; AppliedDirectiveInterfaceFieldLocation location = new AppliedDirectiveInterfaceFieldLocation(interfaze.getName(), field.getName()); @@ -246,40 +246,9 @@ private void appliedDirectiveAdded(EditOperation editOperation) { Vertex appliedDirective = editOperation.getTargetVertex(); Vertex container = newSchemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); if (container.isOfType(SchemaGraph.FIELD)) { - Vertex field = container; - Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); - if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { - Vertex object = interfaceOrObjective; - - if (isObjectAdded(object.getName())) { - return; - } - if (isFieldNewForExistingObject(object.getName(), field.getName())) { - return; - } - AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); - AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); - } - }else if(container.isOfType(SchemaGraph.ARGUMENT)) { - Vertex argument = container; - Vertex field = newSchemaGraph.getFieldOrDirectiveForArgument(argument); - Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); - if(interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { - Vertex object = interfaceOrObjective; - if (isObjectAdded(object.getName())) { - return; - } - if (isFieldNewForExistingObject(object.getName(), field.getName())) { - return; - } - if (isArgumentNewForExistingObjectField(object.getName(), field.getName(), argument.getName())) { - return; - } - AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(),argument.getName()); - AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); - } + appliedDirectiveAddedToField(appliedDirective, container); + } else if (container.isOfType(SchemaGraph.ARGUMENT)) { + appliedDirectiveAddedToArgument(appliedDirective, container); } else if (container.isOfType(SchemaGraph.OBJECT)) { Vertex object = container; @@ -356,6 +325,60 @@ private void appliedDirectiveAdded(EditOperation editOperation) { } } + private void appliedDirectiveAddedToField(Vertex appliedDirective, Vertex container) { + Vertex field = container; + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + + if (isObjectAdded(object.getName())) { + return; + } + if (isFieldNewForExistingObject(object.getName(), field.getName())) { + return; + } + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } + } + + private void appliedDirectiveAddedToArgument(Vertex appliedDirective, Vertex container) { + Vertex argument = container; + Vertex field = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + if (isObjectAdded(object.getName())) { + return; + } + if (isFieldNewForExistingObject(object.getName(), field.getName())) { + return; + } + if (isArgumentNewForExistingObjectField(object.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(), argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } else { + assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = interfaceOrObjective; + if (isInterfaceAdded(interfaze.getName())) { + return; + } + if (isFieldNewForExistingInterface(interfaze.getName(), field.getName())) { + return; + } + if (isArgumentNewForExistingInterfaceField(interfaze.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveInterfaceFieldArgumentLocation location = new AppliedDirectiveInterfaceFieldArgumentLocation(interfaze.getName(), field.getName(), argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveAddition); + } + } + private void handleTypeChanges(List editOperations, Mapping mapping) { for (EditOperation editOperation : editOperations) { Edge newEdge = editOperation.getTargetEdge(); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index da24e57de6..865ad97343 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1313,6 +1313,30 @@ public String getArgumentName() { } } + class AppliedDirectiveInterfaceFieldArgumentLocation implements AppliedDirectiveLocationDetail { + private final String interfaceName; + private final String fieldName; + private final String argumentName; + + public AppliedDirectiveInterfaceFieldArgumentLocation(String interfaceName, String fieldName, String argumentName) { + this.interfaceName = interfaceName; + this.fieldName = fieldName; + this.argumentName = argumentName; + } + + public String getInterfaceName() { + return interfaceName; + } + + public String getFieldName() { + return fieldName; + } + + public String getArgumentName() { + return argumentName; + } + } + class AppliedDirectiveUnionLocation implements AppliedDirectiveLocationDetail { private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index 669c855ed9..8f60e33b83 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -2,6 +2,7 @@ package graphql.schema.diffing.ana import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing +import spock.lang.Ignore import spock.lang.Specification import static graphql.schema.diffing.ana.SchemaDifference.* @@ -395,6 +396,38 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ appliedDirective[0].name == "d" } + @Ignore + def "applied directive added interface field argument"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query implements I { + foo(arg: String) : String + } + interface I { + foo(arg: String): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query implements I { + foo(arg: String) : String + } + interface I { + foo(arg: String @d): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).interfaceName == "I" + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).fieldName == "foo" + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } + From ae51c2d98d97646250cc3e7007e55386c3fe9196 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 07:57:48 +1000 Subject: [PATCH 252/294] applied directives on arguments are not relevant when checked if an interface is implemented correctly --- .../schema/idl/ImplementingTypesChecker.java | 5 ++-- .../schema/idl/SchemaTypeCheckerTest.groovy | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/idl/ImplementingTypesChecker.java b/src/main/java/graphql/schema/idl/ImplementingTypesChecker.java index 82ce0fdd4d..6f2a22ec10 100644 --- a/src/main/java/graphql/schema/idl/ImplementingTypesChecker.java +++ b/src/main/java/graphql/schema/idl/ImplementingTypesChecker.java @@ -186,8 +186,9 @@ private void checkArgumentConsistency( if (objectArg == null) { errors.add(new MissingInterfaceFieldArgumentsError(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef)); } else { - String interfaceArgStr = AstPrinter.printAstCompact(interfaceArg); - String objectArgStr = AstPrinter.printAstCompact(objectArg); + // we need to remove the not relevant applied directives on the argument definitions to compare + String interfaceArgStr = AstPrinter.printAstCompact(interfaceArg.transform(builder -> builder.directives(emptyList()))); + String objectArgStr = AstPrinter.printAstCompact(objectArg.transform(builder -> builder.directives(emptyList()))); if (!interfaceArgStr.equals(objectArgStr)) { errors.add(new InterfaceFieldArgumentRedefinitionError(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef, objectArgStr, interfaceArgStr)); } diff --git a/src/test/groovy/graphql/schema/idl/SchemaTypeCheckerTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaTypeCheckerTest.groovy index 0ce9956d74..59e027073b 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaTypeCheckerTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaTypeCheckerTest.groovy @@ -708,6 +708,35 @@ class SchemaTypeCheckerTest extends Specification { } + def "directives on arguments are not relevant"() { + def spec = """ + directive @d on ARGUMENT_DEFINITION + interface InterfaceType { + fieldB(arg1 : String = "defaultVal", arg2 : String @d, arg3 : Int @d) : String + } + + type BaseType { + fieldX : Int + } + + extend type BaseType implements InterfaceType { + fieldB(arg1 : String = "defaultVal" @d, arg2 : String, arg3 : Int) : String + } + + schema { + query : BaseType + } + """ + + def result = check(spec) + + expect: + result.isEmpty() + + } + + + def "test field arguments on object can contain additional optional arguments"() { def spec = """ interface InterfaceType { From d4474d93be2f434f6fdef2ff4559e2b877135611 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 08:06:43 +1000 Subject: [PATCH 253/294] work on higher level schema diff --- ...rationAnalyzerAppliedDirectivesTest.groovy | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index 8f60e33b83..83310a7973 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -2,12 +2,32 @@ package graphql.schema.diffing.ana import graphql.TestUtil import graphql.schema.diffing.SchemaDiffing -import spock.lang.Ignore import spock.lang.Specification -import static graphql.schema.diffing.ana.SchemaDifference.* +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentDeletion +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentRename +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumValueLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInputObjectFieldLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInputObjectLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceFieldArgumentLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceFieldLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldArgumentLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveScalarLocation +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveUnionLocation +import static graphql.schema.diffing.ana.SchemaDifference.EnumModification +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectModification +import static graphql.schema.diffing.ana.SchemaDifference.InterfaceModification +import static graphql.schema.diffing.ana.SchemaDifference.ObjectModification +import static graphql.schema.diffing.ana.SchemaDifference.ScalarModification +import static graphql.schema.diffing.ana.SchemaDifference.UnionModification -class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ +class EditOperationAnalyzerAppliedDirectivesTest extends Specification { def "applied directive argument deleted interface field "() { given: @@ -396,7 +416,6 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification{ appliedDirective[0].name == "d" } - @Ignore def "applied directive added interface field argument"() { given: def oldSdl = ''' From 04df386b8e01b8a58c4ce87ff5d9dcf9593a2c35 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 10:58:22 +1000 Subject: [PATCH 254/294] improve test case by adding a new valid location example --- .../graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy index 13c23cf883..887fe97f5c 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy @@ -3,12 +3,12 @@ package graphql.schema.idl import graphql.Scalars import graphql.schema.GraphQLScalarType import graphql.schema.idl.errors.DirectiveIllegalLocationError +import graphql.schema.idl.errors.DirectiveIllegalReferenceError import graphql.schema.idl.errors.DirectiveMissingNonNullArgumentError import graphql.schema.idl.errors.DirectiveUndeclaredError import graphql.schema.idl.errors.DirectiveUnknownArgumentError import graphql.schema.idl.errors.IllegalNameError import graphql.schema.idl.errors.NotAnInputTypeError -import graphql.schema.idl.errors.DirectiveIllegalReferenceError import spock.lang.Specification class SchemaTypeDirectivesCheckerTest extends Specification { @@ -27,6 +27,8 @@ class SchemaTypeDirectivesCheckerTest extends Specification { ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + directive @d(arg: String @testDirective) on FIELD + type ObjectType @testDirective(knownArg : "x") { field(arg1 : String @testDirective(knownArg : "x")) : String @testDirective(knownArg : "x") From 532929860a7b27fcf25c091e0b27b7875784b2ac Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 11:19:38 +1000 Subject: [PATCH 255/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 96 +++++++++++++------ .../schema/diffing/ana/SchemaDifference.java | 21 +++- ...rationAnalyzerAppliedDirectivesTest.groovy | 29 +++++- 3 files changed, 113 insertions(+), 33 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index c5c5335f80..f3624a83b9 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -17,10 +17,21 @@ import java.util.Map; import static graphql.Assert.assertTrue; -import static graphql.schema.diffing.ana.SchemaDifference.*; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentDeletion; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDirectiveArgumentLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumValueLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInputObjectFieldLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInputObjectLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceFieldArgumentLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceFieldLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInterfaceLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldArgumentLocation; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectFieldLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveScalarLocation; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveUnionLocation; import static graphql.schema.diffing.ana.SchemaDifference.DirectiveAddition; import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentAddition; import static graphql.schema.diffing.ana.SchemaDifference.DirectiveArgumentDefaultValueModification; @@ -39,6 +50,11 @@ import static graphql.schema.diffing.ana.SchemaDifference.InputObjectAddition; import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDeletion; import static graphql.schema.diffing.ana.SchemaDifference.InputObjectDifference; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectFieldAddition; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectFieldDefaultValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectFieldDeletion; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectFieldRename; +import static graphql.schema.diffing.ana.SchemaDifference.InputObjectFieldTypeModification; import static graphql.schema.diffing.ana.SchemaDifference.InputObjectModification; import static graphql.schema.diffing.ana.SchemaDifference.InterfaceAddition; import static graphql.schema.diffing.ana.SchemaDifference.InterfaceDeletion; @@ -345,37 +361,52 @@ private void appliedDirectiveAddedToField(Vertex appliedDirective, Vertex contai private void appliedDirectiveAddedToArgument(Vertex appliedDirective, Vertex container) { Vertex argument = container; - Vertex field = newSchemaGraph.getFieldOrDirectiveForArgument(argument); - Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); - if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { - Vertex object = interfaceOrObjective; - if (isObjectAdded(object.getName())) { - return; - } - if (isFieldNewForExistingObject(object.getName(), field.getName())) { - return; - } - if (isArgumentNewForExistingObjectField(object.getName(), field.getName(), argument.getName())) { - return; + Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + Vertex field = fieldOrDirective; + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + if (isObjectAdded(object.getName())) { + return; + } + if (isFieldNewForExistingObject(object.getName(), field.getName())) { + return; + } + if (isArgumentNewForExistingObjectField(object.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(), argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } else { + assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = interfaceOrObjective; + if (isInterfaceAdded(interfaze.getName())) { + return; + } + if (isFieldNewForExistingInterface(interfaze.getName(), field.getName())) { + return; + } + if (isArgumentNewForExistingInterfaceField(interfaze.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveInterfaceFieldArgumentLocation location = new AppliedDirectiveInterfaceFieldArgumentLocation(interfaze.getName(), field.getName(), argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveAddition); } - AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(), argument.getName()); - AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); } else { - assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); - Vertex interfaze = interfaceOrObjective; - if (isInterfaceAdded(interfaze.getName())) { - return; - } - if (isFieldNewForExistingInterface(interfaze.getName(), field.getName())) { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + if (isDirectiveAdded(directive.getName())) { return; } - if (isArgumentNewForExistingInterfaceField(interfaze.getName(), field.getName(), argument.getName())) { + if (isArgumentNewForExistingDirective(directive.getName(), argument.getName())) { return; } - AppliedDirectiveInterfaceFieldArgumentLocation location = new AppliedDirectiveInterfaceFieldArgumentLocation(interfaze.getName(), field.getName(), argument.getName()); + AppliedDirectiveDirectiveArgumentLocation location = new AppliedDirectiveDirectiveArgumentLocation(directive.getName(), argument.getName()); AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveAddition); + getDirectiveModification(directive.getName()).getDetails().add(appliedDirectiveAddition); } } @@ -721,7 +752,8 @@ private void changedTypeVertex(EditOperation editOperation) { } - private void typeEdgeInserted(EditOperation editOperation, List editOperations, Mapping mapping) { + private void typeEdgeInserted(EditOperation editOperation, List editOperations, Mapping + mapping) { Edge newEdge = editOperation.getTargetEdge(); Vertex from = newEdge.getFrom(); if (from.isOfType(SchemaGraph.FIELD)) { @@ -734,7 +766,8 @@ private void typeEdgeInserted(EditOperation editOperation, List e } - private void typeEdgeInsertedForInputField(EditOperation editOperation, List editOperations, Mapping mapping) { + private void typeEdgeInsertedForInputField(EditOperation + editOperation, List editOperations, Mapping mapping) { Vertex inputField = editOperation.getTargetEdge().getFrom(); Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); if (isInputObjectAdded(inputObject.getName())) { @@ -750,7 +783,8 @@ private void typeEdgeInsertedForInputField(EditOperation editOperation, List editOperations, Mapping mapping) { + private void typeEdgeInsertedForArgument(EditOperation + editOperation, List editOperations, Mapping mapping) { Vertex argument = editOperation.getTargetEdge().getFrom(); Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { @@ -819,7 +853,8 @@ private void typeEdgeInsertedForArgument(EditOperation editOperation, List editOperations, Mapping mapping) { + private void typeEdgeInsertedForField(EditOperation + editOperation, List editOperations, Mapping mapping) { Vertex field = editOperation.getTargetEdge().getFrom(); Vertex objectOrInterface = newSchemaGraph.getFieldsContainerForField(field); if (objectOrInterface.isOfType(SchemaGraph.OBJECT)) { @@ -861,7 +896,8 @@ private void typeEdgeInsertedForField(EditOperation editOperation, List editOperations, Mapping mapping) { + private EditOperation findDeletedEdge(Vertex targetVertexFrom, List editOperations, Mapping + mapping) { Vertex sourceVertexFrom = mapping.getSource(targetVertexFrom); for (EditOperation editOperation : editOperations) { if (editOperation.getOperation() == EditOperation.Operation.DELETE_EDGE) { diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 865ad97343..6524ceb918 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1313,6 +1313,24 @@ public String getArgumentName() { } } + class AppliedDirectiveDirectiveArgumentLocation implements AppliedDirectiveLocationDetail { + private final String directiveName; + private final String argumentName; + + public AppliedDirectiveDirectiveArgumentLocation(String directiveName, String argumentName) { + this.directiveName = directiveName; + this.argumentName = argumentName; + } + + public String getDirectiveName() { + return directiveName; + } + + public String getArgumentName() { + return argumentName; + } + } + class AppliedDirectiveInterfaceFieldArgumentLocation implements AppliedDirectiveLocationDetail { private final String interfaceName; private final String fieldName; @@ -1416,7 +1434,8 @@ class AppliedDirectiveAddition implements ScalarModificationDetail, EnumModificationDetail, InputObjectModificationDetail, - UnionModificationDetail { + UnionModificationDetail, + DirectiveModificationDetail { private final AppliedDirectiveLocationDetail locationDetail; private final String name; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index 83310a7973..547b853438 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -8,6 +8,7 @@ import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAdditi import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentDeletion import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentRename import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDirectiveArgumentLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumValueLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveInputObjectFieldLocation @@ -20,6 +21,7 @@ import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObject import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveObjectLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveScalarLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveUnionLocation +import static graphql.schema.diffing.ana.SchemaDifference.DirectiveModification import static graphql.schema.diffing.ana.SchemaDifference.EnumModification import static graphql.schema.diffing.ana.SchemaDifference.InputObjectModification import static graphql.schema.diffing.ana.SchemaDifference.InterfaceModification @@ -447,8 +449,31 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification { appliedDirective[0].name == "d" } - - + def "applied directive added directive argument "() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + directive @d2(arg:String) on ARGUMENT_DEFINITION + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + directive @d2(arg:String @d) on ARGUMENT_DEFINITION + type Query { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d2"] instanceof DirectiveModification + def appliedDirective = (changes.directiveDifferences["d2"] as DirectiveModification).getDetails(AppliedDirectiveAddition) + (appliedDirective[0].locationDetail as AppliedDirectiveDirectiveArgumentLocation).directiveName == "d2" + (appliedDirective[0].locationDetail as AppliedDirectiveDirectiveArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } EditOperationAnalysisResult calcDiff( From df2a71764b6aa0fbfcecfdf7e9be3732f9cf0a70 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sat, 12 Nov 2022 15:13:54 +1100 Subject: [PATCH 256/294] Adds an error builder on GraphQLError (#3011) * An error builder on GraphQLError * An error builder on GraphQLError - wat * ErrorClassification builder support * ErrorClassification builder support - readme example * ErrorClassification builder support - renamed method --- .../java/graphql/ErrorClassification.java | 17 ++++ src/main/java/graphql/GraphQLError.java | 82 +++++++++++++++++++ .../java/graphql/GraphqlErrorBuilder.java | 4 +- .../graphql/GraphqlErrorBuilderTest.groovy | 15 ++++ src/test/groovy/readme/ReadmeExamples.java | 6 +- 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/ErrorClassification.java b/src/main/java/graphql/ErrorClassification.java index e23f172810..db9764f2ce 100644 --- a/src/main/java/graphql/ErrorClassification.java +++ b/src/main/java/graphql/ErrorClassification.java @@ -23,4 +23,21 @@ public interface ErrorClassification { default Object toSpecification(GraphQLError error) { return String.valueOf(this); } + + /** + * This produces a simple ErrorClassification that represents the provided String. You can + * use this factory method to give out simple but custom error classifications. + * + * @param errorClassification the string that represents the error classification + * + * @return a ErrorClassification that is that provided string + */ + static ErrorClassification errorClassification(String errorClassification) { + return new ErrorClassification() { + @Override + public String toString() { + return errorClassification; + } + }; + } } diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 74534bca64..b4fc6fa600 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -1,7 +1,9 @@ package graphql; +import graphql.execution.ResultPath; import graphql.language.SourceLocation; +import org.jetbrains.annotations.Nullable; import java.io.Serializable; import java.util.List; @@ -66,5 +68,85 @@ default Map getExtensions() { return null; } + /** + * @return a new builder of {@link GraphQLError}s + */ + static Builder newError() { + return new GraphqlErrorBuilder<>(); + } + + /** + * A builder of {@link GraphQLError}s + */ + interface Builder> { + + /** + * Sets the message of the error using {@link String#format(String, Object...)} with the arguments + * + * @param message the message + * @param formatArgs the arguments to use + * + * @return this builder + */ + B message(String message, Object... formatArgs); + /** + * This adds locations to the error + * + * @param locations the locations to add + * + * @return this builder + */ + B locations(@Nullable List locations); + + /** + * This adds a location to the error + * + * @param location the locations to add + * + * @return this builder + */ + B location(@Nullable SourceLocation location); + + /** + * Sets the path of the message + * + * @param path can be null + * + * @return this builder + */ + B path(@Nullable ResultPath path); + + /** + * Sets the path of the message + * + * @param path can be null + * + * @return this builder + */ + B path(@Nullable List path); + + /** + * Sets the {@link ErrorClassification} of the message + * + * @param errorType the error classification to use + * + * @return this builder + */ + B errorType(ErrorClassification errorType); + + /** + * Sets the extensions of the message + * + * @param extensions the extensions to use + * + * @return this builder + */ + B extensions(@Nullable Map extensions); + + /** + * @return a newly built GraphqlError + */ + GraphQLError build(); + } } diff --git a/src/main/java/graphql/GraphqlErrorBuilder.java b/src/main/java/graphql/GraphqlErrorBuilder.java index ee974ea5ee..4cef5beabe 100644 --- a/src/main/java/graphql/GraphqlErrorBuilder.java +++ b/src/main/java/graphql/GraphqlErrorBuilder.java @@ -4,8 +4,8 @@ import graphql.execution.ResultPath; import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; - import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -20,7 +20,7 @@ */ @SuppressWarnings("unchecked") @PublicApi -public class GraphqlErrorBuilder> { +public class GraphqlErrorBuilder> implements GraphQLError.Builder { private String message; private List path; diff --git a/src/test/groovy/graphql/GraphqlErrorBuilderTest.groovy b/src/test/groovy/graphql/GraphqlErrorBuilderTest.groovy index 59eb7ba8d3..944e1fef35 100644 --- a/src/test/groovy/graphql/GraphqlErrorBuilderTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorBuilderTest.groovy @@ -137,4 +137,19 @@ class GraphqlErrorBuilderTest extends Specification { error.path == null error.extensions == null } + + def "can use a builder direct from graphql error"() { + when: + def error = GraphQLError.newError().message("msg") + .locations(null) + .extensions([x : "y"]) + .path(null) + .build() + then: + error.message == "msg" + error.locations == null + error.extensions == [x : "y"] + error.path == null + + } } \ No newline at end of file diff --git a/src/test/groovy/readme/ReadmeExamples.java b/src/test/groovy/readme/ReadmeExamples.java index 9be296d7bf..6eb795279e 100644 --- a/src/test/groovy/readme/ReadmeExamples.java +++ b/src/test/groovy/readme/ReadmeExamples.java @@ -1,5 +1,6 @@ package readme; +import graphql.ErrorClassification; import graphql.GraphQLError; import graphql.GraphqlErrorBuilder; import graphql.InvalidSyntaxError; @@ -470,7 +471,10 @@ public SpecialError build() { } private void errorBuilderExample() { - GraphQLError err = GraphqlErrorBuilder.newError().message("direct").build(); + GraphQLError err = GraphQLError.newError() + .message("direct") + .errorType(ErrorClassification.errorClassification("customClassification")) + .build(); SpecialError specialErr = new SpecialErrorBuilder().message("special").build(); } From e7b8071ae358b517882346a7197849e285eef482 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 19:53:08 +1000 Subject: [PATCH 257/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 262 +++++++++++++++++- .../schema/diffing/ana/SchemaDifference.java | 26 +- ...rationAnalyzerAppliedDirectivesTest.groovy | 52 ++++ 3 files changed, 338 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index f3624a83b9..ba3eaad97c 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -20,6 +20,7 @@ import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAddition; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentDeletion; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification; +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDeletion; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDirectiveArgumentLocation; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumLocation; import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumValueLocation; @@ -193,7 +194,9 @@ private void handleAppliedDirectives(List editOperations, Mapping } break; case DELETE_VERTEX: - if (editOperation.getSourceVertex().isOfType(SchemaGraph.APPLIED_ARGUMENT)) { + if (editOperation.getSourceVertex().isOfType(SchemaGraph.APPLIED_DIRECTIVE)) { + appliedDirectiveDeleted(editOperation); + } else if (editOperation.getSourceVertex().isOfType(SchemaGraph.APPLIED_ARGUMENT)) { appliedDirectiveArgumentDeleted(editOperation); } break; @@ -203,6 +206,88 @@ private void handleAppliedDirectives(List editOperations, Mapping } + private void appliedDirectiveDeleted(EditOperation editOperation) { + Vertex appliedDirective = editOperation.getSourceVertex(); + Vertex container = oldSchemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); + if (container.isOfType(SchemaGraph.FIELD)) { + appliedDirectiveDeletedFromField(appliedDirective, container); + } else if (container.isOfType(SchemaGraph.ARGUMENT)) { + appliedDirectiveDeletedFromArgument(appliedDirective, container); + } else if (container.isOfType(SchemaGraph.OBJECT)) { + Vertex object = container; + if (isObjectDeleted(object.getName())) { + return; + } + AppliedDirectiveObjectLocation location = new AppliedDirectiveObjectLocation(object.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.INTERFACE)) { + Vertex interfaze = container; + if (isInterfaceDeleted(interfaze.getName())) { + return; + } + AppliedDirectiveInterfaceLocation location = new AppliedDirectiveInterfaceLocation(interfaze.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.SCALAR)) { + Vertex scalar = container; + if (isScalarDeleted(scalar.getName())) { + return; + } + AppliedDirectiveScalarLocation location = new AppliedDirectiveScalarLocation(scalar.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getScalarModification(scalar.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.ENUM)) { + Vertex enumVertex = container; + if (isEnumDeleted(enumVertex.getName())) { + return; + } + AppliedDirectiveEnumLocation location = new AppliedDirectiveEnumLocation(enumVertex.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.ENUM_VALUE)) { + Vertex enumValue = container; + Vertex enumVertex = oldSchemaGraph.getEnumForEnumValue(enumValue); + if (isEnumDeleted(enumVertex.getName())) { + return; + } + if (isEnumValueDeletedFromExistingEnum(enumVertex.getName(), enumValue.getName())) { + return; + } + AppliedDirectiveEnumValueLocation location = new AppliedDirectiveEnumValueLocation(enumVertex.getName(), enumValue.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getEnumModification(enumVertex.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.INPUT_OBJECT)) { + Vertex inputObject = container; + if (isInputObjectDeleted(inputObject.getName())) { + return; + } + AppliedDirectiveInputObjectLocation location = new AppliedDirectiveInputObjectLocation(inputObject.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getInputObjectModification(inputObject.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.INPUT_FIELD)) { + Vertex inputField = container; + Vertex inputObject = oldSchemaGraph.getInputObjectForInputField(inputField); + if (isInputObjectDeleted(inputObject.getName())) { + return; + } + if (isInputFieldDeletedFromExistingInputObject(inputObject.getName(), inputField.getName())) { + return; + } + AppliedDirectiveInputObjectFieldLocation location = new AppliedDirectiveInputObjectFieldLocation(inputObject.getName(), inputField.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getInputObjectModification(inputObject.getName()).getDetails().add(appliedDirectiveDeletion); + } else if (container.isOfType(SchemaGraph.UNION)) { + Vertex union = container; + if (isUnionDeleted(union.getName())) { + return; + } + AppliedDirectiveUnionLocation location = new AppliedDirectiveUnionLocation(union.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getUnionModification(union.getName()).getDetails().add(appliedDirectiveDeletion); + } + } + private void appliedDirectiveArgumentDeleted(EditOperation editOperation) { Vertex deletedArgument = editOperation.getSourceVertex(); Vertex appliedDirective = oldSchemaGraph.getAppliedDirectiveForAppliedArgument(deletedArgument); @@ -341,6 +426,24 @@ private void appliedDirectiveAdded(EditOperation editOperation) { } } + private void appliedDirectiveDeletedFromField(Vertex appliedDirective, Vertex container) { + Vertex field = container; + Vertex interfaceOrObjective = oldSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + + if (isObjectDeleted(object.getName())) { + return; + } + if (isFieldDeletedFromExistingObject(object.getName(), field.getName())) { + return; + } + AppliedDirectiveObjectFieldLocation location = new AppliedDirectiveObjectFieldLocation(object.getName(), field.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveDeletion); + } + } + private void appliedDirectiveAddedToField(Vertex appliedDirective, Vertex container) { Vertex field = container; Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); @@ -359,6 +462,57 @@ private void appliedDirectiveAddedToField(Vertex appliedDirective, Vertex contai } } + private void appliedDirectiveDeletedFromArgument(Vertex appliedDirective, Vertex container) { + Vertex argument = container; + Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(argument); + if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { + Vertex field = fieldOrDirective; + Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { + Vertex object = interfaceOrObjective; + if (isObjectDeleted(object.getName())) { + return; + } + if (isFieldDeletedFromExistingObject(object.getName(), field.getName())) { + return; + } + if (isArgumentDeletedFromExistingObjectField(object.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(), argument.getName()); + AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + } else { + assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); + Vertex interfaze = interfaceOrObjective; + if (isInterfaceDeleted(interfaze.getName())) { + return; + } + if (isFieldDeletedFromExistingInterface(interfaze.getName(), field.getName())) { + return; + } + if (isArgumentDeletedFromExistingInterfaceField(interfaze.getName(), field.getName(), argument.getName())) { + return; + } + AppliedDirectiveInterfaceFieldArgumentLocation location = new AppliedDirectiveInterfaceFieldArgumentLocation(interfaze.getName(), field.getName(), argument.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getInterfaceModification(interfaze.getName()).getDetails().add(appliedDirectiveDeletion); + } + } else { + assertTrue(fieldOrDirective.isOfType(SchemaGraph.DIRECTIVE)); + Vertex directive = fieldOrDirective; + if (isDirectiveDeleted(directive.getName())) { + return; + } + if (isArgumentDeletedFromExistingDirective(directive.getName(), argument.getName())) { + return; + } + AppliedDirectiveDirectiveArgumentLocation location = new AppliedDirectiveDirectiveArgumentLocation(directive.getName(), argument.getName()); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getDirectiveModification(directive.getName()).getDetails().add(appliedDirectiveDeletion); + } + } + private void appliedDirectiveAddedToArgument(Vertex appliedDirective, Vertex container) { Vertex argument = container; Vertex fieldOrDirective = newSchemaGraph.getFieldOrDirectiveForArgument(argument); @@ -1098,6 +1252,10 @@ private boolean isDirectiveAdded(String name) { return directiveDifferences.containsKey(name) && directiveDifferences.get(name) instanceof DirectiveAddition; } + private boolean isDirectiveDeleted(String name) { + return directiveDifferences.containsKey(name) && directiveDifferences.get(name) instanceof DirectiveDeletion; + } + private boolean isObjectAdded(String name) { return objectDifferences.containsKey(name) && objectDifferences.get(name) instanceof ObjectAddition; } @@ -1142,6 +1300,18 @@ private boolean isNewInputFieldExistingInputObject(String inputObjectName, Strin return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); } + private boolean isInputFieldDeletedFromExistingInputObject(String inputObjectName, String fieldName) { + if (!inputObjectDifferences.containsKey(inputObjectName)) { + return false; + } + if (!(inputObjectDifferences.get(inputObjectName) instanceof InputObjectModification)) { + return false; + } + InputObjectModification modification = (InputObjectModification) inputObjectDifferences.get(inputObjectName); + List deletedFields = modification.getDetails(InputObjectFieldDeletion.class); + return deletedFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } + private boolean isArgumentNewForExistingDirective(String directiveName, String argumentName) { if (!directiveDifferences.containsKey(directiveName)) { return false; @@ -1154,6 +1324,18 @@ private boolean isArgumentNewForExistingDirective(String directiveName, String a return newArgs.stream().anyMatch(detail -> detail.getName().equals(argumentName)); } + private boolean isArgumentDeletedFromExistingDirective(String directiveName, String argumentName) { + if (!directiveDifferences.containsKey(directiveName)) { + return false; + } + if (!(directiveDifferences.get(directiveName) instanceof DirectiveModification)) { + return false; + } + DirectiveModification directiveModification = (DirectiveModification) directiveDifferences.get(directiveName); + List deletedArgs = directiveModification.getDetails(DirectiveArgumentDeletion.class); + return deletedArgs.stream().anyMatch(detail -> detail.getName().equals(argumentName)); + } + private boolean isArgumentNewForExistingObjectField(String objectName, String fieldName, String argumentName) { if (!objectDifferences.containsKey(objectName)) { return false; @@ -1173,6 +1355,44 @@ private boolean isArgumentNewForExistingObjectField(String objectName, String fi return newArgs.stream().anyMatch(detail -> detail.getFieldName().equals(fieldName) && detail.getName().equals(argumentName)); } + private boolean isArgumentDeletedFromExistingObjectField(String objectName, String fieldName, String argumentName) { + if (!objectDifferences.containsKey(objectName)) { + return false; + } + if (!(objectDifferences.get(objectName) instanceof ObjectModification)) { + return false; + } + // finding out if the field was just added + ObjectModification objectModification = (ObjectModification) objectDifferences.get(objectName); + List deletedFields = objectModification.getDetails(ObjectFieldDeletion.class); + boolean deletedField = deletedFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + if (deletedField) { + return false; + } + // now finding out if the argument is deleted + List deletedArgs = objectModification.getDetails(ObjectFieldArgumentDeletion.class); + return deletedArgs.stream().anyMatch(detail -> detail.getFieldName().equals(fieldName) && detail.getName().equals(argumentName)); + } + + private boolean isArgumentDeletedFromExistingInterfaceField(String interfaceName, String fieldName, String argumentName) { + if (!interfaceDifferences.containsKey(interfaceName)) { + return false; + } + if (!(interfaceDifferences.get(interfaceName) instanceof InterfaceModification)) { + return false; + } + // finding out if the field was just added + InterfaceModification interfaceModification = (InterfaceModification) interfaceDifferences.get(interfaceName); + List deletedFields = interfaceModification.getDetails(InterfaceFieldDeletion.class); + boolean deletedField = deletedFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + if (deletedField) { + return false; + } + // now finding out if the argument is deleted + List deletedArgs = interfaceModification.getDetails(InterfaceFieldArgumentDeletion.class); + return deletedArgs.stream().anyMatch(detail -> detail.getFieldName().equals(fieldName) && detail.getName().equals(argumentName)); + } + private boolean isArgumentNewForExistingInterfaceField(String objectName, String fieldName, String argumentName) { if (!interfaceDifferences.containsKey(objectName)) { return false; @@ -1204,6 +1424,30 @@ private boolean isFieldNewForExistingObject(String objectName, String fieldName) return newFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); } + private boolean isFieldDeletedFromExistingInterface(String interfaceName, String fieldName) { + if (!interfaceDifferences.containsKey(interfaceName)) { + return false; + } + if (!(interfaceDifferences.get(interfaceName) instanceof InterfaceModification)) { + return false; + } + InterfaceModification interfaceModification = (InterfaceModification) interfaceDifferences.get(interfaceName); + List deletedFields = interfaceModification.getDetails(InterfaceFieldDeletion.class); + return deletedFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } + + private boolean isFieldDeletedFromExistingObject(String objectName, String fieldName) { + if (!objectDifferences.containsKey(objectName)) { + return false; + } + if (!(objectDifferences.get(objectName) instanceof ObjectModification)) { + return false; + } + ObjectModification objectModification = (ObjectModification) objectDifferences.get(objectName); + List deletedFields = objectModification.getDetails(ObjectFieldDeletion.class); + return deletedFields.stream().anyMatch(detail -> detail.getName().equals(fieldName)); + } + private boolean isNewEnumValueForExistingEnum(String enumName, String valueName) { if (!enumDifferences.containsKey(enumName)) { return false; @@ -1216,6 +1460,18 @@ private boolean isNewEnumValueForExistingEnum(String enumName, String valueName) return newValues.stream().anyMatch(detail -> detail.getName().equals(valueName)); } + private boolean isEnumValueDeletedFromExistingEnum(String enumName, String valueName) { + if (!enumDifferences.containsKey(enumName)) { + return false; + } + if (!(enumDifferences.get(enumName) instanceof EnumModification)) { + return false; + } + EnumModification enumModification = (EnumModification) enumDifferences.get(enumName); + List deletedValues = enumModification.getDetails(EnumValueDeletion.class); + return deletedValues.stream().anyMatch(detail -> detail.getName().equals(valueName)); + } + private boolean isFieldNewForExistingInterface(String interfaceName, String fieldName) { if (!interfaceDifferences.containsKey(interfaceName)) { return false; @@ -1244,6 +1500,10 @@ private boolean isScalarAdded(String name) { return scalarDifferences.containsKey(name) && scalarDifferences.get(name) instanceof ScalarAddition; } + private boolean isScalarDeleted(String name) { + return scalarDifferences.containsKey(name) && scalarDifferences.get(name) instanceof ScalarDeletion; + } + private ObjectModification getObjectModification(String newName) { if (!objectDifferences.containsKey(newName)) { objectDifferences.put(newName, new ObjectModification(newName)); diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 6524ceb918..8fd9fd6b4b 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1453,7 +1453,31 @@ public AppliedDirectiveLocationDetail getLocationDetail() { } } - class AppliedDirectiveDeletion { + class AppliedDirectiveDeletion implements + ObjectModificationDetail, + InterfaceModificationDetail, + ScalarModificationDetail, + EnumModificationDetail, + InputObjectModificationDetail, + UnionModificationDetail, + DirectiveModificationDetail { + + private final AppliedDirectiveLocationDetail locationDetail; + private final String name; + + public AppliedDirectiveDeletion(AppliedDirectiveLocationDetail locationDetail, String name) { + this.locationDetail = locationDetail; + this.name = name; + } + + public String getName() { + return name; + } + + public AppliedDirectiveLocationDetail getLocationDetail() { + return locationDetail; + } + } diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index 547b853438..3f2dda06b4 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -8,6 +8,7 @@ import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveAdditi import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentDeletion import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentRename import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveArgumentValueModification +import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDeletion import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveDirectiveArgumentLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumLocation import static graphql.schema.diffing.ana.SchemaDifference.AppliedDirectiveEnumValueLocation @@ -475,6 +476,57 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification { appliedDirective[0].name == "d" } + def "applied directive deleted object"() { + given: + def oldSdl = ''' + directive @d(arg: String) on OBJECT + + type Query @d(arg: "foo") { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg:String) on OBJECT + + type Query { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectLocation).name == "Query" + appliedDirective[0].name == "d" + } + + def "applied directive deleted directive argument "() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + directive @d2(arg:String @d) on ARGUMENT_DEFINITION + type Query { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + directive @d2(arg:String) on ARGUMENT_DEFINITION + type Query { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.directiveDifferences["d2"] instanceof DirectiveModification + def appliedDirective = (changes.directiveDifferences["d2"] as DirectiveModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveDirectiveArgumentLocation).directiveName == "d2" + (appliedDirective[0].locationDetail as AppliedDirectiveDirectiveArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From 4b1f46e9de28cf247ea8a668b81432dc9df7af50 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 19:58:55 +1000 Subject: [PATCH 258/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 2 +- ...rationAnalyzerAppliedDirectivesTest.groovy | 174 ++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index ba3eaad97c..63a04cfde0 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -467,7 +467,7 @@ private void appliedDirectiveDeletedFromArgument(Vertex appliedDirective, Vertex Vertex fieldOrDirective = oldSchemaGraph.getFieldOrDirectiveForArgument(argument); if (fieldOrDirective.isOfType(SchemaGraph.FIELD)) { Vertex field = fieldOrDirective; - Vertex interfaceOrObjective = newSchemaGraph.getFieldsContainerForField(field); + Vertex interfaceOrObjective = oldSchemaGraph.getFieldsContainerForField(field); if (interfaceOrObjective.isOfType(SchemaGraph.OBJECT)) { Vertex object = interfaceOrObjective; if (isObjectDeleted(object.getName())) { diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index 3f2dda06b4..d1da0e8589 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -527,6 +527,180 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification { appliedDirective[0].name == "d" } + def "applied directive deleted enum"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM + enum E @d(arg: "foo") { A, B } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM + enum E { A, B } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumLocation).name == "E" + appliedDirective[0].name == "d" + } + + def "applied directive deleted enum value"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B @d(arg: "foo") } + type Query { + foo: E + } + ''' + def newSdl = ''' + directive @d(arg:String) on ENUM_VALUE + enum E { A, B } + type Query { + foo: E + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.enumDifferences["E"] instanceof EnumModification + def appliedDirective = (changes.enumDifferences["E"] as EnumModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).enumName == "E" + (appliedDirective[0].locationDetail as AppliedDirectiveEnumValueLocation).valueName == "B" + appliedDirective[0].name == "d" + } + + + def "applied directive deleted input object"() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I @d(arg: "foo") { + a: String + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_OBJECT + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectLocation).name == "I" + appliedDirective[0].name == "d" + } + + def "applied directive deleted input object field "() { + given: + def oldSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String @d(arg: "foo") + } + type Query { + foo(arg: I): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INPUT_FIELD_DEFINITION + input I { + a: String + } + type Query { + foo(arg: I): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.inputObjectDifferences["I"] instanceof InputObjectModification + def appliedDirective = (changes.inputObjectDifferences["I"] as InputObjectModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).inputObjectName == "I" + (appliedDirective[0].locationDetail as AppliedDirectiveInputObjectFieldLocation).fieldName == "a" + appliedDirective[0].name == "d" + } + + def "applied directive deleted interface"() { + given: + def oldSdl = ''' + directive @d(arg: String) on INTERFACE + + type Query implements I { + foo: String + } + interface I @d(arg: "foo") { + foo: String + } + ''' + def newSdl = ''' + directive @d(arg:String) on INTERFACE + + type Query implements I{ + foo: String + } + interface I { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceLocation).name == "I" + appliedDirective[0].name == "d" + } + + + def "applied directive deleted interface field argument"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query implements I { + foo(arg: String) : String + } + interface I { + foo(arg: String @d): String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query implements I { + foo(arg: String) : String + } + interface I { + foo(arg: String): String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.interfaceDifferences["I"] instanceof InterfaceModification + def appliedDirective = (changes.interfaceDifferences["I"] as InterfaceModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).interfaceName == "I" + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).fieldName == "foo" + (appliedDirective[0].locationDetail as AppliedDirectiveInterfaceFieldArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From 90ad29dfcdb089b87253d6d0ccfaa5dbb10f768e Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Sat, 12 Nov 2022 20:05:09 +1000 Subject: [PATCH 259/294] work on higher level schema diff --- .../diffing/ana/EditOperationAnalyzer.java | 4 +- ...rationAnalyzerAppliedDirectivesTest.groovy | 107 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 63a04cfde0..0bd06dc768 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -480,8 +480,8 @@ private void appliedDirectiveDeletedFromArgument(Vertex appliedDirective, Vertex return; } AppliedDirectiveObjectFieldArgumentLocation location = new AppliedDirectiveObjectFieldArgumentLocation(object.getName(), field.getName(), argument.getName()); - AppliedDirectiveAddition appliedDirectiveAddition = new AppliedDirectiveAddition(location, appliedDirective.getName()); - getObjectModification(object.getName()).getDetails().add(appliedDirectiveAddition); + AppliedDirectiveDeletion appliedDirectiveDeletion = new AppliedDirectiveDeletion(location, appliedDirective.getName()); + getObjectModification(object.getName()).getDetails().add(appliedDirectiveDeletion); } else { assertTrue(interfaceOrObjective.isOfType(SchemaGraph.INTERFACE)); Vertex interfaze = interfaceOrObjective; diff --git a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy index d1da0e8589..4e1c30a21d 100644 --- a/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/ana/EditOperationAnalyzerAppliedDirectivesTest.groovy @@ -701,6 +701,113 @@ class EditOperationAnalyzerAppliedDirectivesTest extends Specification { appliedDirective[0].name == "d" } + def "applied directive deleted object field"() { + given: + def oldSdl = ''' + directive @d(arg: String) on FIELD_DEFINITION + + type Query { + foo: String @d(arg: "foo") + } + ''' + def newSdl = ''' + directive @d(arg:String) on FIELD_DEFINITION + + type Query { + foo: String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).objectName == "Query" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldLocation).fieldName == "foo" + appliedDirective[0].name == "d" + } + + def "applied directive deleted object field argument"() { + given: + def oldSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query { + foo(arg: String @d(arg: "foo")) : String + } + ''' + def newSdl = ''' + directive @d(arg:String) on ARGUMENT_DEFINITION + type Query { + foo(arg: String) : String + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.objectDifferences["Query"] instanceof ObjectModification + def appliedDirective = (changes.objectDifferences["Query"] as ObjectModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).objectName == "Query" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).fieldName == "foo" + (appliedDirective[0].locationDetail as AppliedDirectiveObjectFieldArgumentLocation).argumentName == "arg" + appliedDirective[0].name == "d" + } + + + def "applied directive deleted scalar"() { + given: + def oldSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime @d(arg: "foo") + type Query { + foo: DateTime + } + ''' + def newSdl = ''' + directive @d(arg:String) on SCALAR + scalar DateTime + type Query { + foo: DateTime + } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.scalarDifferences["DateTime"] instanceof ScalarModification + def appliedDirective = (changes.scalarDifferences["DateTime"] as ScalarModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveScalarLocation).name == "DateTime" + appliedDirective[0].name == "d" + } + + + def "applied directive deleted union"() { + given: + def oldSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U @d(arg: "foo") = A | B + type A { a: String } + type B { b: String } + ''' + def newSdl = ''' + directive @d(arg: String) on UNION + type Query { + foo: U + } + union U = A | B + type A { a: String } + type B { b: String } + ''' + when: + def changes = calcDiff(oldSdl, newSdl) + then: + changes.unionDifferences["U"] instanceof UnionModification + def appliedDirective = (changes.unionDifferences["U"] as UnionModification).getDetails(AppliedDirectiveDeletion) + (appliedDirective[0].locationDetail as AppliedDirectiveUnionLocation).name == "U" + appliedDirective[0].name == "d" + } + EditOperationAnalysisResult calcDiff( String oldSdl, From 04215b5cc9d2b4cc6a1431ee69eee02acdb21398 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 13 Nov 2022 12:07:29 +1100 Subject: [PATCH 260/294] Fix grammar --- src/main/resources/i18n/Parsing_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/i18n/Parsing_de.properties b/src/main/resources/i18n/Parsing_de.properties index 8b2d73b430..9438eaf8aa 100644 --- a/src/main/resources/i18n/Parsing_de.properties +++ b/src/main/resources/i18n/Parsing_de.properties @@ -17,7 +17,7 @@ InvalidSyntax.noMessage=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} InvalidSyntax.full=Ung\u00fcltige Syntax, ANTLR-Fehler ''{0}'' in Zeile {1} Spalte {2} InvalidSyntaxBail.noToken=Ung\u00fcltige Syntax in Zeile {0} Spalte {1} -InvalidSyntaxBail.full=Ung\u00fcltige Syntax wegen ung\u00fcltigem Token ''{0}'' in Zeile {1} Spalte {2} +InvalidSyntaxBail.full=Ung\u00fcltige Syntax wegen des ung\u00fcltigen Tokens ''{0}'' in Zeile {1} Spalte {2} # InvalidSyntaxMoreTokens.full=Es wurde eine ung\u00fcltige Syntax festgestellt. Es gibt zus\u00e4tzliche Token im Text, die nicht konsumiert wurden. Ung\u00fcltiges Token ''{0}'' in Zeile {1} Spalte {2} # From 4f1af63eebb770e462d3769c7ce69752d8265482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Tomi=C4=87?= Date: Sun, 20 Nov 2022 02:28:04 +0100 Subject: [PATCH 261/294] TypeResolutionEnvironment#getLocalContext seems accidentally non-public (#3021) --- src/main/java/graphql/TypeResolutionEnvironment.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/TypeResolutionEnvironment.java b/src/main/java/graphql/TypeResolutionEnvironment.java index 5e6a18fc86..c606fdd5fe 100644 --- a/src/main/java/graphql/TypeResolutionEnvironment.java +++ b/src/main/java/graphql/TypeResolutionEnvironment.java @@ -4,7 +4,6 @@ import graphql.execution.DataFetcherResult; import graphql.execution.MergedField; import graphql.execution.TypeResolutionParameters; -import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; @@ -89,7 +88,7 @@ public GraphQLSchema getSchema() { /** * Returns the context object set in via {@link ExecutionInput#getContext()} * - * @param to two + * @param the type to cast the result to * * @return the context object * @@ -112,11 +111,11 @@ public GraphQLContext getGraphQLContext() { /** * Returns the local context object set in via {@link DataFetcherResult#getLocalContext()} * - * @param to two + * @param the type to cast the result to * * @return the local context object */ - T getLocalContext() { + public T getLocalContext() { //noinspection unchecked return (T) localContext; } From 1a78669830c551659e0cc69ac4ed016bbb9ded1c Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Sun, 20 Nov 2022 02:29:49 +0100 Subject: [PATCH 262/294] centralizing resource loading in BenchmarkUtils (#3022) --- src/test/java/benchmark/AddError.java | 2 +- src/test/java/benchmark/BenchMark.java | 7 ++----- src/test/java/benchmark/BenchmarkUtils.java | 6 +++++- .../benchmark/DFSelectionSetBenchmark.java | 15 ++------------- .../benchmark/IntrospectionBenchmark.java | 18 +----------------- src/test/java/benchmark/NQBenchmark1.java | 17 ++--------------- src/test/java/benchmark/NQBenchmark2.java | 19 +++---------------- .../java/benchmark/NQExtraLargeBenchmark.java | 16 ++-------------- .../OverlappingFieldValidationBenchmark.java | 15 ++------------- src/test/java/benchmark/SchemaBenchMark.java | 13 +------------ .../benchmark/SchemaTransformerBenchmark.java | 7 +------ .../java/benchmark/ValidatorBenchmark.java | 16 ++-------------- 12 files changed, 24 insertions(+), 127 deletions(-) diff --git a/src/test/java/benchmark/AddError.java b/src/test/java/benchmark/AddError.java index b1c53dbc9d..950b756ab3 100644 --- a/src/test/java/benchmark/AddError.java +++ b/src/test/java/benchmark/AddError.java @@ -18,7 +18,7 @@ @State(Scope.Benchmark) public class AddError { - private ExecutionContext context = new ExecutionContextBuilder() + private final ExecutionContext context = new ExecutionContextBuilder() .executionId(ExecutionId.generate()) .build(); diff --git a/src/test/java/benchmark/BenchMark.java b/src/test/java/benchmark/BenchMark.java index 95ee823cad..626fafc87f 100644 --- a/src/test/java/benchmark/BenchMark.java +++ b/src/test/java/benchmark/BenchMark.java @@ -17,8 +17,6 @@ import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Warmup; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -44,7 +42,7 @@ public class BenchMark { @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) - public void benchMarkSimpleQueriesThroughput() { + public void benchMarkSimpleQueriesThroughput() { executeQuery(); } @@ -61,8 +59,7 @@ public static void executeQuery() { } private static GraphQL buildGraphQL() { - InputStream sdl = BenchMark.class.getClassLoader().getResourceAsStream("starWarsSchema.graphqls"); - TypeDefinitionRegistry definitionRegistry = new SchemaParser().parse(new InputStreamReader(sdl)); + TypeDefinitionRegistry definitionRegistry = new SchemaParser().parse(BenchmarkUtils.loadResource("starWarsSchema.graphqls")); DataFetcher heroDataFetcher = environment -> CharacterDTO.mkCharacter(environment, "r2d2", NUMBER_OF_FRIENDS); diff --git a/src/test/java/benchmark/BenchmarkUtils.java b/src/test/java/benchmark/BenchmarkUtils.java index ec7b44a1fc..fd7897e125 100644 --- a/src/test/java/benchmark/BenchmarkUtils.java +++ b/src/test/java/benchmark/BenchmarkUtils.java @@ -1,6 +1,7 @@ package benchmark; import com.google.common.io.Files; +import graphql.Assert; import java.io.File; import java.net.URL; @@ -9,11 +10,14 @@ public class BenchmarkUtils { + @SuppressWarnings("UnstableApiUsage") static String loadResource(String name) { return asRTE(() -> { URL resource = BenchmarkUtils.class.getClassLoader().getResource(name); + if (resource == null) { + throw new IllegalArgumentException("missing resource: " + name); + } return String.join("\n", Files.readLines(new File(resource.toURI()), Charset.defaultCharset())); - }); } diff --git a/src/test/java/benchmark/DFSelectionSetBenchmark.java b/src/test/java/benchmark/DFSelectionSetBenchmark.java index 1e54b32647..2687a444a0 100644 --- a/src/test/java/benchmark/DFSelectionSetBenchmark.java +++ b/src/test/java/benchmark/DFSelectionSetBenchmark.java @@ -1,7 +1,5 @@ package benchmark; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; import graphql.execution.CoercedVariables; import graphql.language.Document; import graphql.normalized.ExecutableNormalizedField; @@ -27,13 +25,9 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import java.io.IOException; -import java.net.URL; import java.util.List; import java.util.concurrent.TimeUnit; -import static com.google.common.io.Resources.getResource; - @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 2) @@ -51,10 +45,10 @@ public static class MyState { @Setup public void setup() { try { - String schemaString = readFromClasspath("large-schema-2.graphqls"); + String schemaString = BenchmarkUtils.loadResource("large-schema-2.graphqls"); schema = SchemaGenerator.createdMockedSchema(schemaString); - String query = readFromClasspath("large-schema-2-query.graphql"); + String query = BenchmarkUtils.loadResource("large-schema-2-query.graphql"); document = Parser.parse(query); ExecutableNormalizedOperation executableNormalizedOperation = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(schema, document, null, CoercedVariables.emptyVariables()); @@ -64,15 +58,10 @@ public void setup() { outputFieldType = schema.getObjectType("Object42"); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark diff --git a/src/test/java/benchmark/IntrospectionBenchmark.java b/src/test/java/benchmark/IntrospectionBenchmark.java index 603924c9e8..6745d07d62 100644 --- a/src/test/java/benchmark/IntrospectionBenchmark.java +++ b/src/test/java/benchmark/IntrospectionBenchmark.java @@ -1,7 +1,5 @@ package benchmark; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; import graphql.ExecutionResult; import graphql.GraphQL; import graphql.execution.DataFetcherResult; @@ -23,15 +21,11 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; -import java.io.IOException; -import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import static com.google.common.io.Resources.getResource; - @State(Scope.Benchmark) public class IntrospectionBenchmark { @@ -79,23 +73,13 @@ private boolean isSchemaTypesFetch(DataFetchingEnvironment env, Object source) { } public IntrospectionBenchmark() { - String largeSchema = readFromClasspath("large-schema-4.graphqls"); + String largeSchema = BenchmarkUtils.loadResource("large-schema-4.graphqls"); GraphQLSchema graphQLSchema = SchemaGenerator.createdMockedSchema(largeSchema); graphQL = GraphQL.newGraphQL(graphQLSchema) //.instrumentation(countingInstrumentation) .build(); } - - private static String readFromClasspath(String file) { - URL url = getResource(file); - try { - return Resources.toString(url, Charsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public static void main(String[] args) { IntrospectionBenchmark introspectionBenchmark = new IntrospectionBenchmark(); // while (true) { diff --git a/src/test/java/benchmark/NQBenchmark1.java b/src/test/java/benchmark/NQBenchmark1.java index f96dd27447..d47d7b7a4c 100644 --- a/src/test/java/benchmark/NQBenchmark1.java +++ b/src/test/java/benchmark/NQBenchmark1.java @@ -1,7 +1,5 @@ package benchmark; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; import graphql.execution.CoercedVariables; import graphql.language.Document; import graphql.normalized.ExecutableNormalizedOperation; @@ -22,13 +20,8 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import java.io.IOException; -import java.net.URL; -import java.util.Collections; import java.util.concurrent.TimeUnit; -import static com.google.common.io.Resources.getResource; - @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 2) @@ -44,21 +37,15 @@ public static class MyState { @Setup public void setup() { try { - String schemaString = readFromClasspath("large-schema-1.graphqls"); + String schemaString = BenchmarkUtils.loadResource("large-schema-1.graphqls"); schema = SchemaGenerator.createdMockedSchema(schemaString); - String query = readFromClasspath("large-schema-1-query.graphql"); + String query = BenchmarkUtils.loadResource("large-schema-1-query.graphql"); document = Parser.parse(query); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark diff --git a/src/test/java/benchmark/NQBenchmark2.java b/src/test/java/benchmark/NQBenchmark2.java index 931e1160f9..68402b4ec7 100644 --- a/src/test/java/benchmark/NQBenchmark2.java +++ b/src/test/java/benchmark/NQBenchmark2.java @@ -1,8 +1,6 @@ package benchmark; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.io.Resources; import graphql.execution.CoercedVariables; import graphql.language.Document; import graphql.language.Field; @@ -28,16 +26,10 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; -import java.io.IOException; -import java.net.URL; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import static com.google.common.io.Resources.getResource; - @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 2) @@ -53,21 +45,16 @@ public static class MyState { @Setup public void setup() { try { - String schemaString = readFromClasspath("large-schema-2.graphqls"); + String schemaString = BenchmarkUtils.loadResource("large-schema-2.graphqls"); schema = SchemaGenerator.createdMockedSchema(schemaString); - String query = readFromClasspath("large-schema-2-query.graphql"); + String query = BenchmarkUtils.loadResource("large-schema-2-query.graphql"); document = Parser.parse(query); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark @@ -77,7 +64,7 @@ private String readFromClasspath(String file) throws IOException { @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) - public ExecutableNormalizedOperation benchMarkAvgTime(MyState myState) throws ExecutionException, InterruptedException { + public ExecutableNormalizedOperation benchMarkAvgTime(MyState myState) { ExecutableNormalizedOperation executableNormalizedOperation = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(myState.schema, myState.document, null, CoercedVariables.emptyVariables()); // System.out.println("fields size:" + normalizedQuery.getFieldToNormalizedField().size()); return executableNormalizedOperation; diff --git a/src/test/java/benchmark/NQExtraLargeBenchmark.java b/src/test/java/benchmark/NQExtraLargeBenchmark.java index 39714382a0..fb92dbda9d 100644 --- a/src/test/java/benchmark/NQExtraLargeBenchmark.java +++ b/src/test/java/benchmark/NQExtraLargeBenchmark.java @@ -1,7 +1,5 @@ package benchmark; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; import graphql.execution.CoercedVariables; import graphql.language.Document; import graphql.normalized.ExecutableNormalizedOperation; @@ -22,12 +20,8 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import java.io.IOException; -import java.net.URL; import java.util.concurrent.TimeUnit; -import static com.google.common.io.Resources.getResource; - @State(Scope.Benchmark) @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 2) @@ -43,21 +37,15 @@ public static class MyState { @Setup public void setup() { try { - String schemaString = readFromClasspath("extra-large-schema-1.graphqls"); + String schemaString = BenchmarkUtils.loadResource("extra-large-schema-1.graphqls"); schema = SchemaGenerator.createdMockedSchema(schemaString); - String query = readFromClasspath("extra-large-schema-1-query.graphql"); + String query = BenchmarkUtils.loadResource("extra-large-schema-1-query.graphql"); document = Parser.parse(query); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark diff --git a/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java b/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java index 87c4579102..d8cf18bb5f 100644 --- a/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java +++ b/src/test/java/benchmark/OverlappingFieldValidationBenchmark.java @@ -1,7 +1,5 @@ package benchmark; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; import graphql.ExecutionResult; import graphql.GraphQL; import graphql.i18n.I18n; @@ -28,14 +26,11 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import java.io.IOException; -import java.net.URL; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; -import static com.google.common.io.Resources.getResource; import static graphql.Assert.assertTrue; @State(Scope.Benchmark) @@ -56,8 +51,8 @@ public static class MyState { @Setup public void setup() { try { - String schemaString = readFromClasspath("large-schema-4.graphqls"); - String query = readFromClasspath("large-schema-4-query.graphql"); + String schemaString = BenchmarkUtils.loadResource("large-schema-4.graphqls"); + String query = BenchmarkUtils.loadResource("large-schema-4-query.graphql"); schema = SchemaGenerator.createdMockedSchema(schemaString); document = Parser.parse(query); @@ -66,15 +61,9 @@ public void setup() { ExecutionResult executionResult = graphQL.execute(query); assertTrue(executionResult.getErrors().size() == 0); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark diff --git a/src/test/java/benchmark/SchemaBenchMark.java b/src/test/java/benchmark/SchemaBenchMark.java index 54636eb53d..23d35427e8 100644 --- a/src/test/java/benchmark/SchemaBenchMark.java +++ b/src/test/java/benchmark/SchemaBenchMark.java @@ -33,7 +33,7 @@ @Measurement(iterations = 3, time = 10, batchSize = 4) public class SchemaBenchMark { - static String largeSDL = createResourceSDL("large-schema-3.graphqls"); + static String largeSDL = BenchmarkUtils.loadResource("large-schema-3.graphqls"); @Benchmark @BenchmarkMode(Mode.Throughput) @@ -54,17 +54,6 @@ private static GraphQLSchema createSchema(String sdl) { return new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING); } - private static String createResourceSDL(String name) { - try { - URL resource = SchemaBenchMark.class.getClassLoader().getResource(name); - File file = new File(resource.toURI()); - return String.join("\n", Files.readLines(file, Charset.defaultCharset())); - } catch (Exception e) { - throw new RuntimeException(e); - } - - } - @SuppressWarnings("InfiniteLoopStatement") /// make this a main method if you want to run it in JProfiler etc.. public static void mainXXX(String[] args) { diff --git a/src/test/java/benchmark/SchemaTransformerBenchmark.java b/src/test/java/benchmark/SchemaTransformerBenchmark.java index c715d63d75..96057aa534 100644 --- a/src/test/java/benchmark/SchemaTransformerBenchmark.java +++ b/src/test/java/benchmark/SchemaTransformerBenchmark.java @@ -100,19 +100,14 @@ public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, Traverser @Setup public void setup() { try { - String schemaString = readFromClasspath("large-schema-3.graphqls"); + String schemaString = BenchmarkUtils.loadResource("large-schema-3.graphqls"); schema = SchemaGenerator.createdMockedSchema(schemaString); txSchema = SchemaTransformer.transformSchema(schema, directiveAdder); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } @Benchmark diff --git a/src/test/java/benchmark/ValidatorBenchmark.java b/src/test/java/benchmark/ValidatorBenchmark.java index e07f544239..9db2384f29 100644 --- a/src/test/java/benchmark/ValidatorBenchmark.java +++ b/src/test/java/benchmark/ValidatorBenchmark.java @@ -1,13 +1,8 @@ package benchmark; -import java.io.IOException; -import java.net.URL; import java.util.Locale; import java.util.concurrent.TimeUnit; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -28,8 +23,6 @@ import graphql.schema.idl.SchemaGenerator; import graphql.validation.Validator; -import static com.google.common.io.Resources.getResource; - import static graphql.Assert.assertTrue; @@ -67,8 +60,8 @@ public void setup() { private Scenario load(String schemaPath, String queryPath) { try { - String schemaString = readFromClasspath(schemaPath); - String query = readFromClasspath(queryPath); + String schemaString = BenchmarkUtils.loadResource(schemaPath); + String query = BenchmarkUtils.loadResource(queryPath); GraphQLSchema schema = SchemaGenerator.createdMockedSchema(schemaString); Document document = Parser.parse(query); @@ -78,15 +71,10 @@ private Scenario load(String schemaPath, String queryPath) { assertTrue(executionResult.getErrors().size() == 0); return new Scenario(schema, document); } catch (Exception e) { - System.out.println(e); throw new RuntimeException(e); } } - private String readFromClasspath(String file) throws IOException { - URL url = getResource(file); - return Resources.toString(url, Charsets.UTF_8); - } } private void run(Scenario scenario) { From 12def0ffe75df332f58b06549862c2462265e5ce Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 20 Nov 2022 20:19:38 +1100 Subject: [PATCH 263/294] Helper for getting fields based on object type name (#3016) --- .../normalized/ExecutableNormalizedField.java | 40 ++++++++---- .../ExecutableNormalizedFieldTest.groovy | 63 +++++++++++++++++++ 2 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 src/test/groovy/graphql/normalized/ExecutableNormalizedFieldTest.groovy diff --git a/src/main/java/graphql/normalized/ExecutableNormalizedField.java b/src/main/java/graphql/normalized/ExecutableNormalizedField.java index d0e62c6f7a..98e527811f 100644 --- a/src/main/java/graphql/normalized/ExecutableNormalizedField.java +++ b/src/main/java/graphql/normalized/ExecutableNormalizedField.java @@ -28,12 +28,14 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; import static graphql.Assert.assertNotNull; import static graphql.Assert.assertTrue; import static graphql.schema.GraphQLTypeUtil.simplePrint; import static graphql.schema.GraphQLTypeUtil.unwrapAll; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; /** * Intentionally Mutable @@ -193,7 +195,7 @@ public boolean hasChildren() { public GraphQLOutputType getType(GraphQLSchema schema) { List fieldDefinitions = getFieldDefinitions(schema); - Set fieldTypes = fieldDefinitions.stream().map(fd -> simplePrint(fd.getType())).collect(Collectors.toSet()); + Set fieldTypes = fieldDefinitions.stream().map(fd -> simplePrint(fd.getType())).collect(toSet()); Assert.assertTrue(fieldTypes.size() == 1, () -> "More than one type ... use getTypes"); return fieldDefinitions.get(0).getType(); } @@ -355,6 +357,29 @@ public List getChildrenWithSameResultKey(String resul return FpKit.filterList(children, child -> child.getResultKey().equals(resultKey)); } + public List getChildren(int includingRelativeLevel) { + List result = new ArrayList<>(); + assertTrue(includingRelativeLevel >= 1, () -> "relative level must be >= 1"); + + this.getChildren().forEach(child -> { + traverseImpl(child, result::add, 1, includingRelativeLevel); + }); + return result; + } + + /** + * This returns the child fields that can be used if the object is of the specified object type + * + * @param objectTypeName the object type + * + * @return a list of child fields that would apply to that object type + */ + public List getChildren(String objectTypeName) { + return children.stream() + .filter(cld -> cld.objectTypeNames.contains(objectTypeName)) + .collect(toList()); + } + public int getLevel() { return level; } @@ -374,19 +399,10 @@ public String toString() { objectTypeNamesToString() + "." + fieldName + ", alias=" + alias + ", level=" + level + - ", children=" + children.stream().map(ExecutableNormalizedField::toString).collect(Collectors.joining("\n")) + + ", children=" + children.stream().map(ExecutableNormalizedField::toString).collect(joining("\n")) + '}'; } - public List getChildren(int includingRelativeLevel) { - List result = new ArrayList<>(); - assertTrue(includingRelativeLevel >= 1, () -> "relative level must be >= 1"); - - this.getChildren().forEach(child -> { - traverseImpl(child, result::add, 1, includingRelativeLevel); - }); - return result; - } public void traverseSubTree(Consumer consumer) { this.getChildren().forEach(child -> { diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedFieldTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedFieldTest.groovy new file mode 100644 index 0000000000..dc3db5daa4 --- /dev/null +++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedFieldTest.groovy @@ -0,0 +1,63 @@ +package graphql.normalized + +import graphql.TestUtil +import graphql.execution.CoercedVariables +import graphql.language.Document +import graphql.schema.GraphQLSchema +import spock.lang.Specification + +class ExecutableNormalizedFieldTest extends Specification { + + def "can get children of object type"() { + + String schema = """ + type Query{ + pets: [Pet] + } + interface Pet { + id: ID + name : String! + } + type Cat implements Pet{ + id: ID + name : String! + meow : String + } + type Dog implements Pet{ + id: ID + name : String! + woof : String + } + """ + GraphQLSchema graphQLSchema = TestUtil.schema(schema) + + String query = """ + { + pets { + id + name + ... on Dog { + woof + } + ... on Cat { + meow + } + } + } + + """ + Document document = TestUtil.parseQuery(query) + + ExecutableNormalizedOperationFactory normalizedOperationFactory = new ExecutableNormalizedOperationFactory() + def normalizedOperation = normalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, document, null, CoercedVariables.emptyVariables()) + + def pets = normalizedOperation.getTopLevelFields()[0] + def allChildren = pets.getChildren() + def dogFields = pets.getChildren("Dog") + + expect: + allChildren.collect { it.name } == ["id", "name", "woof", "meow"] + dogFields.collect { it.name } == ["id", "name", "woof"] + } + +} From eec55e5110a77fe3e9c433697d03c22e4511c25f Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Tue, 22 Nov 2022 17:48:49 -0500 Subject: [PATCH 264/294] Avoiding some duplicated work in Async (#3023) Async.each(list, cfFactory) with factory was delegating back to Async.each(futures), allocating some extra objects (CompletableFuture + List). The current implementation simply delegates to the new Async.CombinedBuilder. --- src/main/java/graphql/execution/Async.java | 30 ++++++---------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/main/java/graphql/execution/Async.java b/src/main/java/graphql/execution/Async.java index b1dd37b527..ce2482bc80 100644 --- a/src/main/java/graphql/execution/Async.java +++ b/src/main/java/graphql/execution/Async.java @@ -135,29 +135,16 @@ public interface CFFactory { CompletableFuture apply(T input, int index, List previousResults); } - public static CompletableFuture> each(List> futures) { - CompletableFuture> overallResult = new CompletableFuture<>(); - - @SuppressWarnings("unchecked") - CompletableFuture[] arrayOfFutures = futures.toArray(new CompletableFuture[0]); - CompletableFuture - .allOf(arrayOfFutures) - .whenComplete((ignored, exception) -> { - if (exception != null) { - overallResult.completeExceptionally(exception); - return; - } - List results = new ArrayList<>(arrayOfFutures.length); - for (CompletableFuture future : arrayOfFutures) { - results.add(future.join()); - } - overallResult.complete(results); - }); - return overallResult; + public static CompletableFuture> each(List> futures) { // TODO: used only in tests now + CombinedBuilder overall = ofExpectedSize(futures.size()); + for (CompletableFuture cf : futures) { + overall.add(cf); + } + return overall.await(); } public static CompletableFuture> each(Collection list, BiFunction> cfFactory) { - List> futures = new ArrayList<>(list.size()); + CombinedBuilder futures = ofExpectedSize(list.size()); int index = 0; for (T t : list) { CompletableFuture cf; @@ -171,8 +158,7 @@ public static CompletableFuture> each(Collection list, BiFunct } futures.add(cf); } - return each(futures); - + return futures.await(); } public static CompletableFuture> eachSequentially(Iterable list, CFFactory cfFactory) { From 477d9a08965831b8a8f14245b428a79336d04624 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Wed, 23 Nov 2022 09:49:20 +1100 Subject: [PATCH 265/294] Record like property access support (#2994) * Record like naming * Record like naming - tweaks * Record like naming - javadoc tweaks * Updated record like code including the Lambda mechanism * Added more tests and a flag to turn Lambda off * JavaDoc update * More work on record like access Moved record like to the front of the search pattern --- .../graphql/schema/PropertyDataFetcher.java | 10 +-- .../schema/PropertyDataFetcherHelper.java | 6 ++ .../graphql/schema/PropertyFetchingImpl.java | 71 ++++++++++++--- .../fetching/LambdaFetchingSupport.java | 50 +++++++---- .../schema/PropertyDataFetcherTest.groovy | 89 +++++++++++++++++++ .../graphql/schema/fetching/ConfusedPojo.java | 28 ++++++ .../fetching/LambdaFetchingSupportTest.groovy | 61 +++++++++++-- .../groovy/graphql/schema/fetching/Pojo.java | 4 + .../SchemaGeneratorDirectiveHelperTest.groovy | 2 + .../schema/somepackage/RecordLikeClass.java | 34 +++++++ .../somepackage/RecordLikeTwoClassesDown.java | 4 + 11 files changed, 317 insertions(+), 42 deletions(-) create mode 100644 src/test/groovy/graphql/schema/fetching/ConfusedPojo.java create mode 100644 src/test/groovy/graphql/schema/somepackage/RecordLikeClass.java create mode 100644 src/test/groovy/graphql/schema/somepackage/RecordLikeTwoClassesDown.java diff --git a/src/main/java/graphql/schema/PropertyDataFetcher.java b/src/main/java/graphql/schema/PropertyDataFetcher.java index f228f19863..3855897334 100644 --- a/src/main/java/graphql/schema/PropertyDataFetcher.java +++ b/src/main/java/graphql/schema/PropertyDataFetcher.java @@ -8,20 +8,20 @@ import java.util.function.Function; /** - * This is the default data fetcher used in graphql-java. It will examine - * maps and POJO java beans for values that match the desired name, typically the field name + * This is the default data fetcher used in graphql-java, and it will examine + * maps, records and POJO java beans for values that match the desired name, typically the field name, * or it will use a provided function to obtain values. - * maps and POJO java beans for values that match the desired name. *

* It uses the following strategies *

    *
  • If the source is null, return null
  • *
  • If the source is a Map, return map.get(propertyName)
  • *
  • If a function is provided, it is used
  • - *
  • Find a public JavaBean getter method named `propertyName`
  • - *
  • Find any getter method named `propertyName` and call method.setAccessible(true)
  • + *
  • Find a public JavaBean getter method named `getPropertyName()` or `isPropertyName()`
  • + *
  • Find any getter method named `getPropertyName()` or `isPropertyName()` and call method.setAccessible(true)
  • *
  • Find a public field named `propertyName`
  • *
  • Find any field named `propertyName` and call field.setAccessible(true)
  • + *
  • Find a public Record like method named `propertyName()`
  • *
  • If this cant find anything, then null is returned
  • *
*

diff --git a/src/main/java/graphql/schema/PropertyDataFetcherHelper.java b/src/main/java/graphql/schema/PropertyDataFetcherHelper.java index a7223763f1..03e7b8ec74 100644 --- a/src/main/java/graphql/schema/PropertyDataFetcherHelper.java +++ b/src/main/java/graphql/schema/PropertyDataFetcherHelper.java @@ -1,6 +1,7 @@ package graphql.schema; import graphql.Internal; +import graphql.VisibleForTesting; /** * This class is the guts of a property data fetcher and also used in AST code to turn @@ -27,6 +28,11 @@ public static boolean setUseSetAccessible(boolean flag) { return impl.setUseSetAccessible(flag); } + @VisibleForTesting + public static boolean setUseLambdaFactory(boolean flag) { + return impl.setUseLambdaFactory(flag); + } + public static boolean setUseNegativeCache(boolean flag) { return impl.setUseNegativeCache(flag); } diff --git a/src/main/java/graphql/schema/PropertyFetchingImpl.java b/src/main/java/graphql/schema/PropertyFetchingImpl.java index 298063cbc4..56d4984a3f 100644 --- a/src/main/java/graphql/schema/PropertyFetchingImpl.java +++ b/src/main/java/graphql/schema/PropertyFetchingImpl.java @@ -31,6 +31,7 @@ public class PropertyFetchingImpl { private final AtomicBoolean USE_SET_ACCESSIBLE = new AtomicBoolean(true); + private final AtomicBoolean USE_LAMBDA_FACTORY = new AtomicBoolean(true); private final AtomicBoolean USE_NEGATIVE_CACHE = new AtomicBoolean(true); private final ConcurrentMap LAMBDA_CACHE = new ConcurrentHashMap<>(); private final ConcurrentMap METHOD_CACHE = new ConcurrentHashMap<>(); @@ -104,7 +105,7 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g // expensive operation here // - Optional> getterOpt = LambdaFetchingSupport.createGetter(object.getClass(), propertyName); + Optional> getterOpt = lambdaGetter(propertyName, object); if (getterOpt.isPresent()) { Function getter = getterOpt.get(); cachedFunction = new CachedLambdaFunction(getter); @@ -113,23 +114,43 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g } boolean dfeInUse = singleArgumentValue != null; + // + // try by record like name - object.propertyName() + try { + MethodFinder methodFinder = (rootClass, methodName) -> findRecordMethod(cacheKey, rootClass, methodName); + return getPropertyViaRecordMethod(object, propertyName, methodFinder, singleArgumentValue); + } catch (NoSuchMethodException ignored) { + } + // + // try by public getters name - object.getPropertyName() try { - MethodFinder methodFinder = (root, methodName) -> findPubliclyAccessibleMethod(cacheKey, root, methodName, dfeInUse); + MethodFinder methodFinder = (rootClass, methodName) -> findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, dfeInUse); + return getPropertyViaGetterMethod(object, propertyName, graphQLType, methodFinder, singleArgumentValue); + } catch (NoSuchMethodException ignored) { + } + // + // try by accessible getters name - object.getPropertyName() + try { + MethodFinder methodFinder = (aClass, methodName) -> findViaSetAccessible(cacheKey, aClass, methodName, dfeInUse); return getPropertyViaGetterMethod(object, propertyName, graphQLType, methodFinder, singleArgumentValue); } catch (NoSuchMethodException ignored) { - try { - MethodFinder methodFinder = (aClass, methodName) -> findViaSetAccessible(cacheKey, aClass, methodName, dfeInUse); - return getPropertyViaGetterMethod(object, propertyName, graphQLType, methodFinder, singleArgumentValue); - } catch (NoSuchMethodException ignored2) { - try { - return getPropertyViaFieldAccess(cacheKey, object, propertyName); - } catch (FastNoSuchMethodException e) { - // we have nothing to ask for, and we have exhausted our lookup strategies - putInNegativeCache(cacheKey); - return null; - } - } } + // + // try by field name - object.propertyName; + try { + return getPropertyViaFieldAccess(cacheKey, object, propertyName); + } catch (NoSuchMethodException ignored) { + } + // we have nothing to ask for, and we have exhausted our lookup strategies + putInNegativeCache(cacheKey); + return null; + } + + private Optional> lambdaGetter(String propertyName, Object object) { + if (USE_LAMBDA_FACTORY.get()) { + return LambdaFetchingSupport.createGetter(object.getClass(), propertyName); + } + return Optional.empty(); } private boolean isNegativelyCached(CacheKey key) { @@ -149,6 +170,11 @@ private interface MethodFinder { Method apply(Class aClass, String s) throws NoSuchMethodException; } + private Object getPropertyViaRecordMethod(Object object, String propertyName, MethodFinder methodFinder, Object singleArgumentValue) throws NoSuchMethodException { + Method method = methodFinder.apply(object.getClass(), propertyName); + return invokeMethod(object, singleArgumentValue, method, takesSingleArgumentTypeAsOnlyArgument(method)); + } + private Object getPropertyViaGetterMethod(Object object, String propertyName, GraphQLType graphQLType, MethodFinder methodFinder, Object singleArgumentValue) throws NoSuchMethodException { if (isBooleanProperty(graphQLType)) { try { @@ -204,6 +230,20 @@ private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class rootClas return rootClass.getMethod(methodName); } + /* + https://docs.oracle.com/en/java/javase/15/language/records.html + + A record class declares a sequence of fields, and then the appropriate accessors, constructors, equals, hashCode, and toString methods are created automatically. + + Records cannot extend any class - so we need only check the root class for a publicly declared method with the propertyName + + However, we won't just restrict ourselves strictly to true records. We will find methods that are record like + and fetch them - e.g. `object.propertyName()` + */ + private Method findRecordMethod(CacheKey cacheKey, Class rootClass, String methodName) throws NoSuchMethodException { + return findPubliclyAccessibleMethod(cacheKey,rootClass,methodName,false); + } + private Method findViaSetAccessible(CacheKey cacheKey, Class aClass, String methodName, boolean dfeInUse) throws NoSuchMethodException { if (!USE_SET_ACCESSIBLE.get()) { throw new FastNoSuchMethodException(methodName); @@ -306,6 +346,9 @@ public void clearReflectionCache() { public boolean setUseSetAccessible(boolean flag) { return USE_SET_ACCESSIBLE.getAndSet(flag); } + public boolean setUseLambdaFactory(boolean flag) { + return USE_LAMBDA_FACTORY.getAndSet(flag); + } public boolean setUseNegativeCache(boolean flag) { return USE_NEGATIVE_CACHE.getAndSet(flag); diff --git a/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java index 9095c66c08..0056819b36 100644 --- a/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java +++ b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; +import java.util.function.Predicate; import static java.util.stream.Collectors.toList; @@ -42,7 +43,13 @@ public static Optional> createGetter(Class sourceCla Function getterFunction = mkCallFunction(sourceClass, candidateMethod.getName(), candidateMethod.getReturnType()); return Optional.of(getterFunction); } catch (Throwable ignore) { + // // if we cant make a dynamic lambda here, then we give up and let the old property fetching code do its thing + // this can happen on runtimes such as GraalVM native where LambdaMetafactory is not supported + // and will throw something like : + // + // com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported. + // at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) } } return Optional.empty(); @@ -50,7 +57,18 @@ public static Optional> createGetter(Class sourceCla private static Method getCandidateMethod(Class sourceClass, String propertyName) { - List allGetterMethods = findGetterMethodsForProperty(sourceClass, propertyName); + // property() methods first + Predicate recordLikePredicate = method -> isRecordLike(method) && propertyName.equals(decapitalize(method.getName())); + List recordLikeMethods = findMethodsForProperty(sourceClass, + recordLikePredicate); + if (!recordLikeMethods.isEmpty()) { + return recordLikeMethods.get(0); + } + + // getProperty() POJO methods next + Predicate getterPredicate = method -> isGetterNamed(method) && propertyName.equals(mkPropertyNameGetter(method)); + List allGetterMethods = findMethodsForProperty(sourceClass, + getterPredicate); List pojoGetterMethods = allGetterMethods.stream() .filter(LambdaFetchingSupport::isPossiblePojoMethod) .collect(toList()); @@ -60,10 +78,8 @@ private static Method getCandidateMethod(Class sourceClass, String propertyNa method = findBestBooleanGetter(pojoGetterMethods); } return checkForSingleParameterPeer(method, allGetterMethods); - } else { - return null; } - + return null; } private static Method checkForSingleParameterPeer(Method candidateMethod, List allMethods) { @@ -88,21 +104,18 @@ private static Method findBestBooleanGetter(List methods) { /** * Finds all methods in a class hierarchy that match the property name - they might not be suitable but they * - * @param sourceClass the class we are looking to work on - * @param propertyName the name of the property + * @param sourceClass the class we are looking to work on * * @return a list of getter methods for that property */ - private static List findGetterMethodsForProperty(Class sourceClass, String propertyName) { + private static List findMethodsForProperty(Class sourceClass, Predicate predicate) { List methods = new ArrayList<>(); Class currentClass = sourceClass; while (currentClass != null) { Method[] declaredMethods = currentClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { - if (isGetterNamed(declaredMethod)) { - if (nameMatches(propertyName, declaredMethod)) { - methods.add(declaredMethod); - } + if (predicate.test(declaredMethod)) { + methods.add(declaredMethod); } } currentClass = currentClass.getSuperclass(); @@ -113,12 +126,6 @@ private static List findGetterMethodsForProperty(Class sourceClass, S .collect(toList()); } - - private static boolean nameMatches(String propertyName, Method declaredMethod) { - String methodPropName = mkPropertyName(declaredMethod); - return propertyName.equals(methodPropName); - } - private static boolean isPossiblePojoMethod(Method method) { return !isObjectMethod(method) && returnsSomething(method) && @@ -127,6 +134,13 @@ private static boolean isPossiblePojoMethod(Method method) { isPublic(method); } + private static boolean isRecordLike(Method method) { + return !isObjectMethod(method) && + returnsSomething(method) && + hasNoParameters(method) && + isPublic(method); + } + private static boolean isBooleanGetter(Method method) { Class returnType = method.getReturnType(); return isGetterNamed(method) && (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)); @@ -153,7 +167,7 @@ private static boolean isObjectMethod(Method method) { return method.getDeclaringClass().equals(Object.class); } - private static String mkPropertyName(Method method) { + private static String mkPropertyNameGetter(Method method) { // // getFooName becomes fooName // isFoo becomes foo diff --git a/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy b/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy index 39f83fa123..3d289c3688 100644 --- a/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy +++ b/src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy @@ -2,9 +2,12 @@ package graphql.schema import graphql.ExecutionInput import graphql.TestUtil +import graphql.schema.fetching.ConfusedPojo import graphql.schema.somepackage.ClassWithDFEMethods import graphql.schema.somepackage.ClassWithInterfaces import graphql.schema.somepackage.ClassWithInteritanceAndInterfaces +import graphql.schema.somepackage.RecordLikeClass +import graphql.schema.somepackage.RecordLikeTwoClassesDown import graphql.schema.somepackage.TestClass import graphql.schema.somepackage.TwoClassesDown import spock.lang.Specification @@ -20,6 +23,7 @@ class PropertyDataFetcherTest extends Specification { PropertyDataFetcher.setUseSetAccessible(true) PropertyDataFetcher.setUseNegativeCache(true) PropertyDataFetcher.clearReflectionCache() + PropertyDataFetcherHelper.setUseLambdaFactory(true) } def env(obj) { @@ -95,6 +99,91 @@ class PropertyDataFetcherTest extends Specification { result == null } + def "fetch via record method"() { + def environment = env(new RecordLikeClass()) + when: + def fetcher = new PropertyDataFetcher("recordProperty") + def result = fetcher.get(environment) + then: + result == "recordProperty" + + // caching works + when: + fetcher = new PropertyDataFetcher("recordProperty") + result = fetcher.get(environment) + then: + result == "recordProperty" + + // recordArgumentMethod will not work because it takes a parameter + when: + fetcher = new PropertyDataFetcher("recordArgumentMethod") + result = fetcher.get(environment) + then: + result == null + + // equals will not work because it takes a parameter + when: + fetcher = new PropertyDataFetcher("equals") + result = fetcher.get(environment) + then: + result == null + + // we allow hashCode() and toString() because why not - they are valid property names + // they might not be that useful but they can be accessed + + when: + fetcher = new PropertyDataFetcher("hashCode") + result = fetcher.get(environment) + then: + result == 666 + + when: + fetcher = new PropertyDataFetcher("toString") + result = fetcher.get(environment) + then: + result == "toString" + } + + def "can fetch record like methods that are public and on super classes"() { + def environment = env(new RecordLikeTwoClassesDown()) + when: + def fetcher = new PropertyDataFetcher("recordProperty") + def result = fetcher.get(environment) + then: + result == "recordProperty" + } + + def "fetch via record method without lambda support"() { + PropertyDataFetcherHelper.setUseLambdaFactory(false) + PropertyDataFetcherHelper.clearReflectionCache() + + when: + def environment = env(new RecordLikeClass()) + def fetcher = new PropertyDataFetcher("recordProperty") + def result = fetcher.get(environment) + then: + result == "recordProperty" + + when: + environment = env(new RecordLikeTwoClassesDown()) + fetcher = new PropertyDataFetcher("recordProperty") + result = fetcher.get(environment) + then: + result == "recordProperty" + } + + def "fetch via record method without lambda support in preference to getter methods"() { + PropertyDataFetcherHelper.setUseLambdaFactory(false) + PropertyDataFetcherHelper.clearReflectionCache() + + when: + def environment = env(new ConfusedPojo()) + def fetcher = new PropertyDataFetcher("recordLike") + def result = fetcher.get(environment) + then: + result == "recordLike" + } + def "fetch via public method"() { def environment = env(new TestClass()) def fetcher = new PropertyDataFetcher("publicProperty") diff --git a/src/test/groovy/graphql/schema/fetching/ConfusedPojo.java b/src/test/groovy/graphql/schema/fetching/ConfusedPojo.java new file mode 100644 index 0000000000..aa0bc174c6 --- /dev/null +++ b/src/test/groovy/graphql/schema/fetching/ConfusedPojo.java @@ -0,0 +1,28 @@ +package graphql.schema.fetching; + +public class ConfusedPojo { + + public String getRecordLike() { + return "getRecordLike"; + } + + public String recordLike() { + return "recordLike"; + } + + public String gettingConfused() { + return "gettingConfused"; + } + + public String getTingConfused() { + return "getTingConfused"; + } + + public boolean issues() { + return true; + } + + public boolean isSues() { + return false; + } +} diff --git a/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy index 19b78cbd03..d3a68f992d 100644 --- a/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy +++ b/src/test/groovy/graphql/schema/fetching/LambdaFetchingSupportTest.groovy @@ -32,23 +32,74 @@ class LambdaFetchingSupportTest extends Specification { } - def "will handle bad methods and missing ones"() { + def "get make getters based on record like names"() { + def pojo = new Pojo("Brad", 42) + when: + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "recordLike") + then: + getter.isPresent() + getter.get().apply(pojo) == "recordLike" + + // + // record like getters will be found first - this is new behavior but more sensible behavior + def confusedPojo = new ConfusedPojo() + when: + getter = LambdaFetchingSupport.createGetter(ConfusedPojo.class, "recordLike") + then: + getter.isPresent() + getter.get().apply(confusedPojo) == "recordLike" + + // weird arse getter methods like `issues` versus `isSues` + when: + getter = LambdaFetchingSupport.createGetter(ConfusedPojo.class, "gettingConfused") + then: + getter.isPresent() + getter.get().apply(confusedPojo) == "gettingConfused" + + when: + getter = LambdaFetchingSupport.createGetter(ConfusedPojo.class, "tingConfused") + then: + getter.isPresent() + getter.get().apply(confusedPojo) == "getTingConfused" + + // weird arse getter methods like `issues` versus `isSues` + when: + getter = LambdaFetchingSupport.createGetter(ConfusedPojo.class, "issues") + then: + getter.isPresent() + getter.get().apply(confusedPojo) == true + + when: + getter = LambdaFetchingSupport.createGetter(ConfusedPojo.class, "sues") + then: + getter.isPresent() + getter.get().apply(confusedPojo) == false + + } + + def "will handle missing ones"() { when: def getter = LambdaFetchingSupport.createGetter(Pojo.class, "nameX") then: !getter.isPresent() + } + + def "will handle weird ones"() { + + def pojo = new Pojo("Brad", 42) when: - getter = LambdaFetchingSupport.createGetter(Pojo.class, "get") + def getter = LambdaFetchingSupport.createGetter(Pojo.class, "get") then: - !getter.isPresent() + getter.isPresent() + getter.get().apply(pojo) == "get" when: getter = LambdaFetchingSupport.createGetter(Pojo.class, "is") then: - !getter.isPresent() - + getter.isPresent() + getter.get().apply(pojo) == "is" } def "can handle boolean setters - is by preference"() { diff --git a/src/test/groovy/graphql/schema/fetching/Pojo.java b/src/test/groovy/graphql/schema/fetching/Pojo.java index 3b478f0712..dac6ce914b 100644 --- a/src/test/groovy/graphql/schema/fetching/Pojo.java +++ b/src/test/groovy/graphql/schema/fetching/Pojo.java @@ -65,4 +65,8 @@ public String is() { return "is"; } + public String recordLike() { + return "recordLike"; + } + } \ No newline at end of file diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy index f021c6542c..e54df617c3 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorDirectiveHelperTest.groovy @@ -14,6 +14,7 @@ import graphql.schema.FieldCoordinates import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLArgument import graphql.schema.GraphQLCodeRegistry +import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLEnumValueDefinition import graphql.schema.GraphQLFieldDefinition @@ -21,6 +22,7 @@ import graphql.schema.GraphQLFieldsContainer import graphql.schema.GraphQLInputObjectField import graphql.schema.GraphQLInputObjectType import graphql.schema.GraphQLInterfaceType +import graphql.schema.GraphQLNamedType import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLScalarType import graphql.schema.GraphQLUnionType diff --git a/src/test/groovy/graphql/schema/somepackage/RecordLikeClass.java b/src/test/groovy/graphql/schema/somepackage/RecordLikeClass.java new file mode 100644 index 0000000000..57cc04f2f1 --- /dev/null +++ b/src/test/groovy/graphql/schema/somepackage/RecordLikeClass.java @@ -0,0 +1,34 @@ +package graphql.schema.somepackage; + +import graphql.schema.DataFetchingEnvironment; + +/** + * This is obviously not an actual record class from Java 14 onwards, but it + * smells like one and that's enough really. Its public, not derived from another + * class and has a public method named after a property + */ +public class RecordLikeClass { + + public String recordProperty() { + return "recordProperty"; + } + + public String recordArgumentMethod(DataFetchingEnvironment environment) { + return "recordArgumentMethod"; + } + + @Override + public int hashCode() { + return 666; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public String toString() { + return "toString"; + } +} diff --git a/src/test/groovy/graphql/schema/somepackage/RecordLikeTwoClassesDown.java b/src/test/groovy/graphql/schema/somepackage/RecordLikeTwoClassesDown.java new file mode 100644 index 0000000000..4e744f2872 --- /dev/null +++ b/src/test/groovy/graphql/schema/somepackage/RecordLikeTwoClassesDown.java @@ -0,0 +1,4 @@ +package graphql.schema.somepackage; + +public class RecordLikeTwoClassesDown extends RecordLikeClass { +} From d75ab956d5c9e6afeab6a4f93d07b783364574ed Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Fri, 25 Nov 2022 14:48:44 +1100 Subject: [PATCH 266/294] Lightweight data fetchers (#2953) * WIP - lightweight data fetchers * WIP - lightweight data fetchers * WIP - lightweight data fetchers - fixing tests - got rid of LightWeight class * WIP - lightweight data fetchers - fixing tests - got rid of LightWeight class -using switch * WIP - lightweight data fetchers - twizzling benchmark * WIP - lightweight data fetchers - twizzling benchmark * WIP - lightweight data fetchers - added Backdoor * WIP - lightweight data fetchers - the old impl was wrong on supplier * Lightweight data fetchers - removed back door * Made a LightDataFetcher interface for specifying that you are lightweight * Revert DataFetcher changes * Merged in works for Light Weight fetching --- .../graphql/execution/ExecutionStrategy.java | 91 ++++++++++-------- .../InstrumentationFieldFetchParameters.java | 16 ++-- .../java/graphql/schema/LightDataFetcher.java | 36 ++++++++ .../graphql/schema/PropertyDataFetcher.java | 21 ++++- .../schema/PropertyDataFetcherHelper.java | 8 +- .../graphql/schema/PropertyFetchingImpl.java | 17 ++-- src/test/groovy/graphql/GraphQLTest.groovy | 92 +++++++++---------- .../AsyncSerialExecutionStrategyTest.groovy | 31 ++++--- .../execution/ExecutionStrategyTest.groovy | 6 +- 9 files changed, 193 insertions(+), 125 deletions(-) create mode 100644 src/main/java/graphql/schema/LightDataFetcher.java diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index 81e4756dc0..33d45f5af7 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -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; @@ -237,68 +238,59 @@ protected CompletableFuture 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 = FpKit.intraThreadMemoize( - () -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType)); - Supplier> argumentValues = () -> executionStepInfo.get().getArguments(); + // if the DF (like PropertyDataFetcher) does not use the arguments or execution step info then dont build any - Supplier normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo); + Supplier dataFetchingEnvironment = FpKit.intraThreadMemoize(() -> { - // DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern - DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalizedFieldSupplier); - QueryDirectives queryDirectives = new QueryDirectivesImpl(field, - executionContext.getGraphQLSchema(), - executionContext.getCoercedVariables().toMap(), - executionContext.getGraphQLContext(), - executionContext.getLocale()); + Supplier executionStepInfo = FpKit.intraThreadMemoize( + () -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType)); + Supplier> argumentValues = () -> executionStepInfo.get().getArguments(); + + Supplier 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 fetchCtx = nonNullCtx(instrumentation.beginFieldFetch(instrumentationFieldFetchParams, executionContext.getInstrumentationState()) ); - CompletableFuture 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 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); } else { return CompletableFuture.completedFuture(result); } @@ -307,6 +299,25 @@ protected CompletableFuture fetchField(ExecutionContext executionC .thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result)); } + private CompletableFuture invokeDataFetcher(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDef, Supplier dataFetchingEnvironment, DataFetcher dataFetcher) { + CompletableFuture 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; + } + protected Supplier getNormalizedField(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Supplier executionStepInfo) { Supplier normalizedQuery = executionContext.getNormalizedQueryTree(); return () -> normalizedQuery.get().getNormalizedField(parameters.getField(), executionStepInfo.get().getObjectType(), executionStepInfo.get().getPath()); diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java index f5b7911eb8..6013214013 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java @@ -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 environment; 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 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 environment, InstrumentationState instrumentationState, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) { + super(getExecutionContext, () -> environment.get().getExecutionStepInfo(), instrumentationState); this.environment = environment; this.executionStrategyParameters = executionStrategyParameters; this.trivialDataFetcher = trivialDataFetcher; @@ -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() { diff --git a/src/main/java/graphql/schema/LightDataFetcher.java b/src/main/java/graphql/schema/LightDataFetcher.java new file mode 100644 index 0000000000..0458457c02 --- /dev/null +++ b/src/main/java/graphql/schema/LightDataFetcher.java @@ -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 for two + */ +public interface LightDataFetcher extends TrivialDataFetcher { + + /** + * 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. + *

+ * 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 environmentSupplier) throws Exception; +} diff --git a/src/main/java/graphql/schema/PropertyDataFetcher.java b/src/main/java/graphql/schema/PropertyDataFetcher.java index 3855897334..77cfdcf062 100644 --- a/src/main/java/graphql/schema/PropertyDataFetcher.java +++ b/src/main/java/graphql/schema/PropertyDataFetcher.java @@ -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 @@ -31,7 +31,7 @@ * @see graphql.schema.DataFetcher */ @PublicApi -public class PropertyDataFetcher implements DataFetcher, TrivialDataFetcher { +public class PropertyDataFetcher implements LightDataFetcher { private final String propertyName; private final Function function; @@ -69,6 +69,7 @@ private PropertyDataFetcher(Function function) { * * @param propertyName the name of the property to retrieve * @param the type of result + * * @return a new PropertyDataFetcher using the provided function as its source of values */ public static PropertyDataFetcher fetching(String propertyName) { @@ -92,6 +93,7 @@ public static PropertyDataFetcher fetching(String propertyName) { * @param function the function to use to obtain a value from the source object * @param the type of the source object * @param the type of result + * * @return a new PropertyDataFetcher using the provided function as its source of values */ public static PropertyDataFetcher fetching(Function function) { @@ -105,10 +107,19 @@ public String getPropertyName() { return propertyName; } - @SuppressWarnings("unchecked") + @Override + public T get(GraphQLFieldDefinition fieldDefinition, Object source, Supplier 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 environmentSupplier) { if (source == null) { return null; } @@ -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); } /** @@ -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) { @@ -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) { diff --git a/src/main/java/graphql/schema/PropertyDataFetcherHelper.java b/src/main/java/graphql/schema/PropertyDataFetcherHelper.java index 03e7b8ec74..2c38b5e127 100644 --- a/src/main/java/graphql/schema/PropertyDataFetcherHelper.java +++ b/src/main/java/graphql/schema/PropertyDataFetcherHelper.java @@ -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 @@ -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 environment) { + return impl.getPropertyValue(propertyName, object, graphQLType, true, environment::get); } public static void clearReflectionCache() { diff --git a/src/main/java/graphql/schema/PropertyFetchingImpl.java b/src/main/java/graphql/schema/PropertyFetchingImpl.java index 56d4984a3f..75a795a0ee 100644 --- a/src/main/java/graphql/schema/PropertyFetchingImpl.java +++ b/src/main/java/graphql/schema/PropertyFetchingImpl.java @@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import static graphql.Assert.assertShouldNeverHappen; import static graphql.Scalars.GraphQLBoolean; @@ -61,7 +62,7 @@ private static final class CachedLambdaFunction { } } - public Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType, Object singleArgumentValue) { + public Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType, boolean dfeInUse, Supplier singleArgumentValue) { if (object instanceof Map) { return ((Map) object).get(propertyName); } @@ -113,7 +114,6 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g return getter.apply(object); } - boolean dfeInUse = singleArgumentValue != null; // // try by record like name - object.propertyName() try { @@ -170,12 +170,12 @@ private interface MethodFinder { Method apply(Class aClass, String s) throws NoSuchMethodException; } - private Object getPropertyViaRecordMethod(Object object, String propertyName, MethodFinder methodFinder, Object singleArgumentValue) throws NoSuchMethodException { + private Object getPropertyViaRecordMethod(Object object, String propertyName, MethodFinder methodFinder, Supplier singleArgumentValue) throws NoSuchMethodException { Method method = methodFinder.apply(object.getClass(), propertyName); return invokeMethod(object, singleArgumentValue, method, takesSingleArgumentTypeAsOnlyArgument(method)); } - private Object getPropertyViaGetterMethod(Object object, String propertyName, GraphQLType graphQLType, MethodFinder methodFinder, Object singleArgumentValue) throws NoSuchMethodException { + private Object getPropertyViaGetterMethod(Object object, String propertyName, GraphQLType graphQLType, MethodFinder methodFinder, Supplier singleArgumentValue) throws NoSuchMethodException { if (isBooleanProperty(graphQLType)) { try { return getPropertyViaGetterUsingPrefix(object, propertyName, "is", methodFinder, singleArgumentValue); @@ -187,7 +187,7 @@ private Object getPropertyViaGetterMethod(Object object, String propertyName, Gr } } - private Object getPropertyViaGetterUsingPrefix(Object object, String propertyName, String prefix, MethodFinder methodFinder, Object singleArgumentValue) throws NoSuchMethodException { + private Object getPropertyViaGetterUsingPrefix(Object object, String propertyName, String prefix, MethodFinder methodFinder, Supplier singleArgumentValue) throws NoSuchMethodException { String getterName = prefix + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); Method method = methodFinder.apply(object.getClass(), getterName); return invokeMethod(object, singleArgumentValue, method, takesSingleArgumentTypeAsOnlyArgument(method)); @@ -302,13 +302,14 @@ private Object getPropertyViaFieldAccess(CacheKey cacheKey, Object object, Strin } } - private Object invokeMethod(Object object, Object singleArgumentValue, Method method, boolean takesSingleArgument) throws FastNoSuchMethodException { + private Object invokeMethod(Object object, Supplier singleArgumentValue, Method method, boolean takesSingleArgument) throws FastNoSuchMethodException { try { if (takesSingleArgument) { - if (singleArgumentValue == null) { + Object argValue = singleArgumentValue.get(); + if (argValue == null) { throw new FastNoSuchMethodException(method.getName()); } - return method.invoke(object, singleArgumentValue); + return method.invoke(object, argValue); } else { return method.invoke(object); } diff --git a/src/test/groovy/graphql/GraphQLTest.groovy b/src/test/groovy/graphql/GraphQLTest.groovy index d6c1b062a1..ec2523d3f8 100644 --- a/src/test/groovy/graphql/GraphQLTest.groovy +++ b/src/test/groovy/graphql/GraphQLTest.groovy @@ -31,6 +31,7 @@ import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLNonNull import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLSchema +import graphql.schema.LightDataFetcher import graphql.schema.StaticDataFetcher import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.errors.SchemaProblem @@ -41,6 +42,7 @@ import spock.lang.Specification import spock.lang.Unroll import java.util.concurrent.CompletableFuture +import java.util.function.Supplier import java.util.function.UnaryOperator import static graphql.ExecutionInput.Builder @@ -427,7 +429,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -446,9 +448,9 @@ class GraphQLTest extends Specification { GraphQL.newGraphQL(schema).build().execute(query) then: - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert !env.arguments.containsKey('bar') + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert !env.arguments.containsKey('bar') } } @@ -458,7 +460,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -472,17 +474,15 @@ class GraphQLTest extends Specification { .argument(newArgument().name("bar").type(GraphQLInt).build())) ).build() def query = "{foo(bar: null)}" - DataFetchingEnvironment dataFetchingEnvironment when: GraphQL.newGraphQL(schema).build().execute(query) then: - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - dataFetchingEnvironment = env - assert env.arguments.containsKey('bar') - assert env.arguments['bar'] == null + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.containsKey('bar') + assert env.arguments['bar'] == null } } @@ -496,7 +496,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -516,12 +516,12 @@ class GraphQLTest extends Specification { then: result.errors.size() == 0 - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert env.arguments.size() == 1 - assert env.arguments["bar"] instanceof Map - assert env.arguments['bar']['someKey'] == 'value' - assert !(env.arguments['bar'] as Map).containsKey('otherKey') + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.size() == 1 + assert env.arguments["bar"] instanceof Map + assert env.arguments['bar']['someKey'] == 'value' + assert !(env.arguments['bar'] as Map).containsKey('otherKey') } } @@ -535,7 +535,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -555,13 +555,13 @@ class GraphQLTest extends Specification { then: result.errors.size() == 0 - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert env.arguments.size() == 1 - assert env.arguments["bar"] instanceof Map - assert env.arguments['bar']['someKey'] == 'value' - assert (env.arguments['bar'] as Map).containsKey('otherKey') - assert env.arguments['bar']['otherKey'] == null + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.size() == 1 + assert env.arguments["bar"] instanceof Map + assert env.arguments['bar']['someKey'] == 'value' + assert (env.arguments['bar'] as Map).containsKey('otherKey') + assert env.arguments['bar']['otherKey'] == null } } @@ -574,7 +574,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -594,11 +594,11 @@ class GraphQLTest extends Specification { then: result.errors.size() == 0 - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert env.arguments.size() == 1 - assert env.arguments["bar"] instanceof Map - assert !(env.arguments['bar'] as Map).containsKey('list') + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.size() == 1 + assert env.arguments["bar"] instanceof Map + assert !(env.arguments['bar'] as Map).containsKey('list') } } @@ -611,7 +611,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -631,12 +631,12 @@ class GraphQLTest extends Specification { then: result.errors.size() == 0 - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert env.arguments.size() == 1 - assert env.arguments["bar"] instanceof Map - assert (env.arguments['bar'] as Map).containsKey('list') - assert env.arguments['bar']['list'] == null + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.size() == 1 + assert env.arguments["bar"] instanceof Map + assert (env.arguments['bar'] as Map).containsKey('list') + assert env.arguments['bar']['list'] == null } } @@ -649,7 +649,7 @@ class GraphQLTest extends Specification { def queryType = "QueryType" def fooName = "foo" def fooCoordinates = FieldCoordinates.coordinates(queryType, fooName) - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry() .dataFetcher(fooCoordinates, dataFetcher) .build() @@ -669,12 +669,12 @@ class GraphQLTest extends Specification { then: result.errors.size() == 0 - 1 * dataFetcher.get(_) >> { - DataFetchingEnvironment env -> - assert env.arguments.size() == 1 - assert env.arguments["bar"] instanceof Map - assert (env.arguments['bar'] as Map).containsKey('list') - assert env.arguments['bar']['list'] == [null] + 1 * dataFetcher.get(_, _, _) >> { + def env = (it[2] as Supplier).get() + assert env.arguments.size() == 1 + assert env.arguments["bar"] instanceof Map + assert (env.arguments['bar'] as Map).containsKey('list') + assert env.arguments['bar']['list'] == [null] } } diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index e35ea56d6d..1d811bd3bd 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -10,6 +10,7 @@ import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema +import graphql.schema.LightDataFetcher import spock.lang.Specification import java.util.concurrent.CompletableFuture @@ -125,13 +126,13 @@ class AsyncSerialExecutionStrategyTest extends Specification { @SuppressWarnings("GroovyAssignabilityCheck") def "async serial execution test"() { given: - def df1 = Mock(DataFetcher) + def df1 = Mock(LightDataFetcher) def cf1 = new CompletableFuture() - def df2 = Mock(DataFetcher) + def df2 = Mock(LightDataFetcher) def cf2 = new CompletableFuture() - def df3 = Mock(DataFetcher) + def df3 = Mock(LightDataFetcher) def cf3 = new CompletableFuture() GraphQLSchema schema = schema(df1, df2, df3) @@ -165,35 +166,35 @@ class AsyncSerialExecutionStrategyTest extends Specification { then: !result.isDone() - 1 * df1.get(_) >> cf1 - 0 * df2.get(_) >> cf2 - 0 * df3.get(_) >> cf3 + 1 * df1.get(_,_,_) >> cf1 + 0 * df2.get(_,_,_) >> cf2 + 0 * df3.get(_,_,_) >> cf3 when: cf1.complete("world1") then: !result.isDone() - 0 * df1.get(_) >> cf1 - 1 * df2.get(_) >> cf2 - 0 * df3.get(_) >> cf3 + 0 * df1.get(_,_,_) >> cf1 + 1 * df2.get(_,_,_) >> cf2 + 0 * df3.get(_,_,_) >> cf3 when: cf2.complete("world2") then: !result.isDone() - 0 * df1.get(_) >> cf1 - 0 * df2.get(_) >> cf2 - 1 * df3.get(_) >> cf3 + 0 * df1.get(_,_,_) >> cf1 + 0 * df2.get(_,_,_) >> cf2 + 1 * df3.get(_,_,_) >> cf3 when: cf3.complete("world3") then: - 0 * df1.get(_) >> cf1 - 0 * df2.get(_) >> cf2 - 0 * df3.get(_) >> cf3 + 0 * df1.get(_,_,_) >> cf1 + 0 * df2.get(_,_,_) >> cf2 + 0 * df3.get(_,_,_) >> cf3 result.isDone() result.get().data == ['hello': 'world1', 'hello2': 'world2', 'hello3': 'world3'] } diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index 63ba108a3e..412ff3fc65 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -28,11 +28,13 @@ import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLScalarType import graphql.schema.GraphQLSchema +import graphql.schema.LightDataFetcher import org.dataloader.DataLoaderRegistry import spock.lang.Specification import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionException +import java.util.function.Supplier import java.util.stream.Stream import static ExecutionStrategyParameters.newParameters @@ -497,7 +499,7 @@ class ExecutionStrategyTest extends Specification { @SuppressWarnings("GroovyVariableNotAssigned") def "resolveField creates correct DataFetchingEnvironment"() { - def dataFetcher = Mock(DataFetcher) + def dataFetcher = Mock(LightDataFetcher) def someFieldName = "someField" def testTypeName = "Type" def fieldDefinition = newFieldDefinition() @@ -542,7 +544,7 @@ class ExecutionStrategyTest extends Specification { executionStrategy.resolveField(executionContext, parameters) then: - 1 * dataFetcher.get(_) >> { args -> environment = args[0] } + 1 * dataFetcher.get(_,_,_) >> { environment = (it[2] as Supplier).get() } environment.fieldDefinition == fieldDefinition environment.graphQLSchema == schema environment.graphQlContext.get("key") == "context" From 57eca2eb67d3f927873f396a9822b6195d4dcbb0 Mon Sep 17 00:00:00 2001 From: Federico Rispo <44871614+federicorispo@users.noreply.github.com> Date: Fri, 25 Nov 2022 04:49:24 +0100 Subject: [PATCH 267/294] Update ValidationError#toString to print the extentions field (#3024) * chore: Update ValidationError#toString to print the extentions field - Updated toString method in ValidationError class to print the extensions field. If the field is null then an empty array will be printed `...extensions=[]` - Added a couple of UTs to test the ValidationError#toString method * chore: Remove message field in ValidationError and initialized the queryPath and extensions fields * Replace HashMap with ImmutableMap --- .../graphql/validation/ValidationError.java | 33 +++++++++++----- .../validation/ValidationErrorToString.groovy | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/test/groovy/graphql/validation/ValidationErrorToString.groovy diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 393661e0d5..841db1c17a 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -1,6 +1,7 @@ package graphql.validation; +import com.google.common.collect.ImmutableMap; import graphql.DeprecatedAt; import graphql.ErrorType; import graphql.GraphQLError; @@ -12,16 +13,16 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @PublicApi public class ValidationError implements GraphQLError { - private final String message; private final List locations = new ArrayList<>(); private final String description; private final ValidationErrorClassification validationErrorType; - private final List queryPath; - private final Map extensions; + private final List queryPath = new ArrayList<>(); + private final ImmutableMap extensions; @Deprecated @DeprecatedAt("2022-07-10") @@ -70,13 +71,16 @@ public ValidationError(ValidationErrorType validationErrorType, List getExtensions() { @Override public String toString() { + String extensionsString = ""; + + if (extensions.size() > 0) { + extensionsString = extensions + .keySet() + .stream() + .map(key -> key + "=" + extensions.get(key)) + .collect(Collectors.joining(", ")); + } + return "ValidationError{" + "validationErrorType=" + validationErrorType + ", queryPath=" + queryPath + - ", message=" + message + + ", message=" + description + ", locations=" + locations + ", description='" + description + '\'' + + ", extensions=[" + extensionsString + ']' + '}'; } diff --git a/src/test/groovy/graphql/validation/ValidationErrorToString.groovy b/src/test/groovy/graphql/validation/ValidationErrorToString.groovy new file mode 100644 index 0000000000..cfade59c28 --- /dev/null +++ b/src/test/groovy/graphql/validation/ValidationErrorToString.groovy @@ -0,0 +1,39 @@ +package graphql.validation + +import graphql.language.SourceLocation +import spock.lang.Specification + +class ValidationErrorToString extends Specification { + + def 'toString prints correctly ValidationError object when all fields are initialized'() { + given: + def sourceLocations = [new SourceLocation(5, 0), new SourceLocation(10, 1)] + def description = "Validation Error (UnknownType)" + def validationErrorClassification = ValidationErrorType.UnknownType + def queryPath = ["home", "address"] + def extensions = ["extension1": "first", "extension2": true, "extension3": 2] + + when: + def validationError = ValidationError + .newValidationError() + .sourceLocations(sourceLocations) + .description(description) + .validationErrorType(validationErrorClassification) + .queryPath(queryPath) + .extensions(extensions) + .build() + + then: + validationError.toString() == "ValidationError{validationErrorType=UnknownType, queryPath=[home, address], message=Validation Error (UnknownType), locations=[SourceLocation{line=5, column=0}, SourceLocation{line=10, column=1}], description='Validation Error (UnknownType)', extensions=[extension1=first, extension2=true, extension3=2]}" + } + + def 'toString prints correctly ValidationError object when all fields are empty'() { + when: + def validationError = ValidationError + .newValidationError() + .build() + + then: + validationError.toString() == "ValidationError{validationErrorType=null, queryPath=[], message=null, locations=[], description='null', extensions=[]}" + } +} From 14ebd6dba07c46d3f57e2566e29867000e107a5f Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Fri, 25 Nov 2022 22:07:47 -0500 Subject: [PATCH 268/294] Minor fixes (#3029) * avoid extra allocation of LinkedHashMap in MergedSelectionSet * javadoc fixes * remove Async.each with List --- src/main/java/graphql/execution/Async.java | 29 +------------------ .../java/graphql/execution/MergedField.java | 7 ++--- .../graphql/execution/MergedSelectionSet.java | 3 +- .../groovy/graphql/execution/AsyncTest.groovy | 11 ------- .../dataloader/DataLoaderHangingTest.groovy | 5 ++-- 5 files changed, 7 insertions(+), 48 deletions(-) diff --git a/src/main/java/graphql/execution/Async.java b/src/main/java/graphql/execution/Async.java index ce2482bc80..cc2f631401 100644 --- a/src/main/java/graphql/execution/Async.java +++ b/src/main/java/graphql/execution/Async.java @@ -2,7 +2,6 @@ import graphql.Assert; import graphql.Internal; -import graphql.collect.ImmutableKit; import java.util.ArrayList; import java.util.Collection; @@ -13,7 +12,6 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.Supplier; @Internal @@ -28,7 +26,7 @@ public interface CombinedBuilder { } /** - * Combines 1 or more CF. It is a wrapper around CompletableFuture.allOf. + * Combines 0 or more CF into one. It is a wrapper around CompletableFuture.allOf. * * @param expectedSize how many we expect * @param for two @@ -135,14 +133,6 @@ public interface CFFactory { CompletableFuture apply(T input, int index, List previousResults); } - public static CompletableFuture> each(List> futures) { // TODO: used only in tests now - CombinedBuilder overall = ofExpectedSize(futures.size()); - for (CompletableFuture cf : futures) { - overall.add(cf); - } - return overall.await(); - } - public static CompletableFuture> each(Collection list, BiFunction> cfFactory) { CombinedBuilder futures = ofExpectedSize(list.size()); int index = 0; @@ -224,21 +214,4 @@ public static CompletableFuture exceptionallyCompletedFuture(Throwable ex return result; } - public static CompletableFuture> flatMap(List inputs, Function> mapper) { - List> collect = ImmutableKit.map(inputs, mapper); - return Async.each(collect); - } - - public static CompletableFuture> map(CompletableFuture> values, Function mapper) { - return values.thenApply(list -> ImmutableKit.map(list, mapper)); - } - - public static List> map(List> values, Function mapper) { - return ImmutableKit.map(values, cf -> cf.thenApply(mapper)); - } - - public static List> mapCompose(List> values, Function> mapper) { - return ImmutableKit.map(values, cf -> cf.thenCompose(mapper)); - } - } diff --git a/src/main/java/graphql/execution/MergedField.java b/src/main/java/graphql/execution/MergedField.java index 4e69afa98b..2ce672bb5b 100644 --- a/src/main/java/graphql/execution/MergedField.java +++ b/src/main/java/graphql/execution/MergedField.java @@ -5,7 +5,6 @@ import graphql.language.Argument; import graphql.language.Field; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -13,7 +12,7 @@ import static graphql.Assert.assertNotEmpty; /** - * This represent all Fields in a query which overlap and are merged into one. + * This represents all Fields in a query which overlap and are merged into one. * This means they all represent the same field actually when the query is executed. * * Example query with more than one Field merged together: @@ -42,7 +41,7 @@ * } * * - * Here the me field is merged together including the sub selections. + * Here the field is merged together including the sub selections. * * A third example with different directives: *
@@ -55,7 +54,7 @@
  * 
* These examples make clear that you need to consider all merged fields together to have the full picture. * - * The actual logic when fields can successfully merged together is implemented in {#graphql.validation.rules.OverlappingFieldsCanBeMerged} + * The actual logic when fields can be successfully merged together is implemented in {#graphql.validation.rules.OverlappingFieldsCanBeMerged} */ @PublicApi public class MergedField { diff --git a/src/main/java/graphql/execution/MergedSelectionSet.java b/src/main/java/graphql/execution/MergedSelectionSet.java index f549cec199..321a82c7ec 100644 --- a/src/main/java/graphql/execution/MergedSelectionSet.java +++ b/src/main/java/graphql/execution/MergedSelectionSet.java @@ -5,7 +5,6 @@ import graphql.Assert; import graphql.PublicApi; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,7 +52,7 @@ public static Builder newMergedSelectionSet() { } public static class Builder { - private Map subFields = new LinkedHashMap<>(); + private Map subFields = ImmutableMap.of(); private Builder() { diff --git a/src/test/groovy/graphql/execution/AsyncTest.groovy b/src/test/groovy/graphql/execution/AsyncTest.groovy index 1ac9650ef8..c79f483b6d 100644 --- a/src/test/groovy/graphql/execution/AsyncTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncTest.groovy @@ -130,15 +130,4 @@ class AsyncTest extends Specification { exception.getCause().getMessage() == "some error" } - def "each works for list of futures "() { - given: - completedFuture('x') - - when: - def result = Async.each([completedFuture('x'), completedFuture('y'), completedFuture('z')]) - - then: - result.isDone() - result.get() == ['x', 'y', 'z'] - } } diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy index 1a5c68c674..b76152f75d 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy @@ -2,7 +2,6 @@ package graphql.execution.instrumentation.dataloader import com.github.javafaker.Faker import graphql.ExecutionInput -import graphql.ExecutionResult import graphql.GraphQL import graphql.TestUtil import graphql.execution.Async @@ -130,7 +129,7 @@ class DataLoaderHangingTest extends Specification { .build() then: "execution shouldn't hang" - List> futures = [] + def futures = Async.ofExpectedSize(NUM_OF_REPS) for (int i = 0; i < NUM_OF_REPS; i++) { DataLoaderRegistry dataLoaderRegistry = mkNewDataLoaderRegistry(executor) @@ -168,7 +167,7 @@ class DataLoaderHangingTest extends Specification { futures.add(result) } // wait for each future to complete and grab the results - Async.each(futures) + futures.await() .whenComplete({ results, error -> if (error) { throw error From dd237fe9d3c435fec51d4da09fb0153456d492ea Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:21:20 +1100 Subject: [PATCH 269/294] Make String parseValue coercing consistent with JS implementation --- .../graphql/scalar/GraphqlStringCoercing.java | 14 +++++-- src/main/resources/i18n/Scalars.properties | 2 + src/main/resources/i18n/Scalars_de.properties | 2 + .../groovy/graphql/ScalarsStringTest.groovy | 38 +++++++++++++++++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlStringCoercing.java b/src/main/java/graphql/scalar/GraphqlStringCoercing.java index 541d157fd9..d794edfe14 100644 --- a/src/main/java/graphql/scalar/GraphqlStringCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlStringCoercing.java @@ -24,11 +24,19 @@ @Internal public class GraphqlStringCoercing implements Coercing { - private String toStringImpl(Object input) { return String.valueOf(input); } + private String parseValueImpl(@NotNull Object input, Locale locale) { + if (!(input instanceof String)) { + throw new CoercingParseValueException( + i18nMsg(locale, "String.unexpectedRawValueType", typeName(input)) + ); + } + return (String) input; + } + private String parseLiteralImpl(@NotNull Object input, Locale locale) { if (!(input instanceof StringValue)) { throw new CoercingParseLiteralException( @@ -56,12 +64,12 @@ public String serialize(@NotNull Object dataFetcherResult) { @Override @Deprecated public String parseValue(@NotNull Object input) { - return toStringImpl(input); + return parseValueImpl(input, Locale.getDefault()); } @Override public String parseValue(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) throws CoercingParseValueException { - return toStringImpl(input); + return parseValueImpl(input, locale); } @Override diff --git a/src/main/resources/i18n/Scalars.properties b/src/main/resources/i18n/Scalars.properties index 0897fe58eb..4dbc68d683 100644 --- a/src/main/resources/i18n/Scalars.properties +++ b/src/main/resources/i18n/Scalars.properties @@ -27,3 +27,5 @@ Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' b # Boolean.notBoolean=Expected a value that can be converted to type ''Boolean'' but it was a ''{0}'' Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}'' +# +String.unexpectedRawValueType=Expected a String input, but it was a ''{0}'' diff --git a/src/main/resources/i18n/Scalars_de.properties b/src/main/resources/i18n/Scalars_de.properties index de929f4770..ce045d8404 100644 --- a/src/main/resources/i18n/Scalars_de.properties +++ b/src/main/resources/i18n/Scalars_de.properties @@ -30,3 +30,5 @@ Float.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''Floa # Boolean.notBoolean=Erwartet wurde ein Wert, der in den Typ ''Boolean'' konvertiert werden kann, aber es war ein ''{0}'' Boolean.unexpectedAstType=Erwartet wurde ein AST type ''BooleanValue'', aber es war ein ''{0}'' +# +String.unexpectedRawValueType=Erwartet wurde eine String-Eingabe, aber es war ein ''{0}'' diff --git a/src/test/groovy/graphql/ScalarsStringTest.groovy b/src/test/groovy/graphql/ScalarsStringTest.groovy index e19e02796a..d4b729af66 100644 --- a/src/test/groovy/graphql/ScalarsStringTest.groovy +++ b/src/test/groovy/graphql/ScalarsStringTest.groovy @@ -4,6 +4,7 @@ import graphql.execution.CoercedVariables import graphql.language.BooleanValue import graphql.language.StringValue import graphql.schema.CoercingParseLiteralException +import graphql.schema.CoercingParseValueException import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll @@ -54,7 +55,6 @@ class ScalarsStringTest extends Specification { def "String serialize #value into #result (#result.class)"() { expect: Scalars.GraphQLString.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result - Scalars.GraphQLString.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result where: value | result @@ -64,10 +64,9 @@ class ScalarsStringTest extends Specification { } @Unroll - def "String serialize #value into #result (#result.class) with deprecated methods"() { + def "String serialize #value into #result (#result.class) with deprecated method"() { expect: Scalars.GraphQLString.getCoercing().serialize(value) == result // Retain deprecated method for test coverage - Scalars.GraphQLString.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage where: value | result @@ -76,4 +75,37 @@ class ScalarsStringTest extends Specification { customObject | "foo" } + @Unroll + def "String parseValue #value into #result"() { + expect: + Scalars.GraphQLString.getCoercing().parseValue("123ab", GraphQLContext.default, Locale.default) == "123ab" + } + + @Unroll + def "String parseValue #value into #result with deprecated method"() { + expect: + Scalars.GraphQLString.getCoercing().parseValue("123ab") == "123ab" // Retain deprecated method for test coverage + } + + @Unroll + def "String parseValue throws exception for non-String values"() { + when: + Scalars.GraphQLString.getCoercing().parseValue(literal, GraphQLContext.default, Locale.default) + then: + def ex = thrown(CoercingParseValueException) + + where: + literal | _ + 123 | _ + true | _ + customObject | _ + } + + def "String parseValue English exception message"() { + when: + Scalars.GraphQLString.getCoercing().parseValue(9001, GraphQLContext.default, Locale.ENGLISH) + then: + def ex = thrown(CoercingParseValueException) + ex.message == "Expected a String input, but it was a 'Integer'" + } } From a05f6c419439ff180605dbfd11c59a49a99448a5 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:50:27 +1100 Subject: [PATCH 270/294] Fix typos --- src/main/java/graphql/scalar/GraphqlStringCoercing.java | 2 +- src/test/groovy/graphql/ScalarsStringTest.groovy | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlStringCoercing.java b/src/main/java/graphql/scalar/GraphqlStringCoercing.java index d794edfe14..b330f254ae 100644 --- a/src/main/java/graphql/scalar/GraphqlStringCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlStringCoercing.java @@ -28,7 +28,7 @@ private String toStringImpl(Object input) { return String.valueOf(input); } - private String parseValueImpl(@NotNull Object input, Locale locale) { + private String parseValueImpl(@NotNull Object input, @NotNull Locale locale) { if (!(input instanceof String)) { throw new CoercingParseValueException( i18nMsg(locale, "String.unexpectedRawValueType", typeName(input)) diff --git a/src/test/groovy/graphql/ScalarsStringTest.groovy b/src/test/groovy/graphql/ScalarsStringTest.groovy index d4b729af66..8a725eb122 100644 --- a/src/test/groovy/graphql/ScalarsStringTest.groovy +++ b/src/test/groovy/graphql/ScalarsStringTest.groovy @@ -75,14 +75,12 @@ class ScalarsStringTest extends Specification { customObject | "foo" } - @Unroll - def "String parseValue #value into #result"() { + def "String parseValue value into result"() { expect: Scalars.GraphQLString.getCoercing().parseValue("123ab", GraphQLContext.default, Locale.default) == "123ab" } - @Unroll - def "String parseValue #value into #result with deprecated method"() { + def "String parseValue value into result with deprecated method"() { expect: Scalars.GraphQLString.getCoercing().parseValue("123ab") == "123ab" // Retain deprecated method for test coverage } From 28cc1f23ce81c6f3bcfa9fbcdffb02db47fe29fe Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:00:11 +1100 Subject: [PATCH 271/294] Fix incorrect bintray redirect --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 609d59347a..45091769f6 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,7 @@ gradle.buildFinished { buildResult -> repositories { mavenCentral() + gradlePluginPortal() mavenLocal() } From 63887824cde9d3bda6fbb1145bf3b8f1bc6cc06d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:03:55 +1100 Subject: [PATCH 272/294] Add pluginManagement --- build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 45091769f6..1f0bdd227a 100644 --- a/build.gradle +++ b/build.gradle @@ -70,10 +70,16 @@ gradle.buildFinished { buildResult -> repositories { mavenCentral() - gradlePluginPortal() mavenLocal() } +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + jar { from "LICENSE.md" from "src/main/antlr/Graphql.g4" From 7515f3a45739d4c5299d9f80692258d398c7e1bb Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:09:45 +1100 Subject: [PATCH 273/294] Amend settings.gradle --- build.gradle | 7 ------- settings.gradle | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 1f0bdd227a..609d59347a 100644 --- a/build.gradle +++ b/build.gradle @@ -73,13 +73,6 @@ repositories { mavenLocal() } -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } -} - jar { from "LICENSE.md" from "src/main/antlr/Graphql.g4" diff --git a/settings.gradle b/settings.gradle index 72827aa7ad..3caa89fd59 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,8 @@ rootProject.name = 'graphql-java' +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} From d4c128229b68709544155b1efffd20f95515f0cc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:13:01 +1100 Subject: [PATCH 274/294] Reorder settings --- settings.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 3caa89fd59..00db1d12d7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,8 @@ -rootProject.name = 'graphql-java' - pluginManagement { repositories { gradlePluginPortal() mavenCentral() } } + +rootProject.name = 'graphql-java' From 9ddffb445f7b2148af342e5574339c4ce16ad9cf Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:15:07 +1100 Subject: [PATCH 275/294] Update jmh plugin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 609d59347a..acfea22bc0 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { id "biz.aQute.bnd.builder" version "6.3.1" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" id "groovy" - id "me.champeau.jmh" version "0.6.6" + id "me.champeau.jmh" version "0.6.8" } def getDevelopmentVersion() { From d1743132ff15d9ee61326b5a2b8671dfb9dff2f4 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:41:26 +1100 Subject: [PATCH 276/294] Prevent metadata redirection for jmh plugin --- build.gradle | 2 +- settings.gradle | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index acfea22bc0..609d59347a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { id "biz.aQute.bnd.builder" version "6.3.1" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" id "groovy" - id "me.champeau.jmh" version "0.6.8" + id "me.champeau.jmh" version "0.6.6" } def getDevelopmentVersion() { diff --git a/settings.gradle b/settings.gradle index 00db1d12d7..17dc6978a0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,14 @@ pluginManagement { repositories { - gradlePluginPortal() mavenCentral() + maven { + url 'https://plugins.gradle.org/m2' + metadataSources { + ignoreGradleMetadataRedirection() + mavenPom() + artifact() + } + } } } From f31ceb4dc7615a2e28e14911c8171a877ff92a9b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 26 Nov 2022 17:00:14 +1100 Subject: [PATCH 277/294] Add comment explaining metadata redirection problem --- settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle b/settings.gradle index 17dc6978a0..160b054c14 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ pluginManagement { maven { url 'https://plugins.gradle.org/m2' metadataSources { + // Avoid redirection to defunct JCenter when Gradle module metadata is not published by a plugin (e.g. JMH plugin) ignoreGradleMetadataRedirection() mavenPom() artifact() From 0264f30af4c2ffb4941521fd2ac373aa6e1cdbb9 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Tue, 29 Nov 2022 12:07:33 +1100 Subject: [PATCH 278/294] This makes sure every introspection type actually has a concrete data fetcher (#3004) * This makes sure every introspection type actually has a concrete data fetcher behind it * Tweak - use IntrospectionDataFetcher again * Tweak - use IntrospectionDataFetcher again - imports * Tweak - use IntrospectionDataFetcher again - code tweak --- .../graphql/introspection/Introspection.java | 134 ++++++++++-------- .../IntrospectionWithDirectivesSupport.java | 11 ++ 2 files changed, 88 insertions(+), 57 deletions(-) diff --git a/src/main/java/graphql/introspection/Introspection.java b/src/main/java/graphql/introspection/Introspection.java index 15c41f3a77..9b874f7f1b 100644 --- a/src/main/java/graphql/introspection/Introspection.java +++ b/src/main/java/graphql/introspection/Introspection.java @@ -40,6 +40,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import static graphql.Assert.assertTrue; @@ -65,6 +66,27 @@ private static void register(GraphQLFieldsContainer parentType, String fieldName introspectionDataFetchers.put(coordinates(parentType.getName(), fieldName), introspectionDataFetcher); } + /** + * To help runtimes such as graalvm, we make sure we have an explicit data fetchers rather then use {@link graphql.schema.PropertyDataFetcher} + * and its reflective mechanisms. This is not reflective because we have the class + * + * @param parentType the containing parent type + * @param fieldName the field name + * @param targetClass the target class of the getter + * @param getter the function to call to get a value of T + * @param for two + */ + private static void register(GraphQLFieldsContainer parentType, String fieldName, Class targetClass, Function getter) { + IntrospectionDataFetcher dataFetcher = env -> { + Object source = env.getSource(); + if (targetClass.isInstance(source)) { + return getter.apply(targetClass.cast(source)); + } + return null; + }; + introspectionDataFetchers.put(coordinates(parentType.getName(), fieldName), dataFetcher); + } + @Internal public static void addCodeForIntrospectionTypes(GraphQLCodeRegistry.Builder codeRegistry) { // place the system __ fields into the mix. They have no parent types @@ -160,7 +182,7 @@ public enum TypeKind { .build(); static { - register(__InputValue, "defaultValue", environment -> { + IntrospectionDataFetcher defaultValueDataFetcher = environment -> { Object type = environment.getSource(); if (type instanceof GraphQLArgument) { GraphQLArgument inputField = (GraphQLArgument) type; @@ -174,8 +196,8 @@ public enum TypeKind { : null; } return null; - }); - register(__InputValue, "isDeprecated", environment -> { + }; + IntrospectionDataFetcher isDeprecatedDataFetcher = environment -> { Object type = environment.getSource(); if (type instanceof GraphQLArgument) { return ((GraphQLArgument) type).isDeprecated(); @@ -183,9 +205,32 @@ public enum TypeKind { return ((GraphQLInputObjectField) type).isDeprecated(); } return null; - }); + }; + IntrospectionDataFetcher typeDataFetcher = environment -> { + Object type = environment.getSource(); + if (type instanceof GraphQLArgument) { + return ((GraphQLArgument) type).getType(); + } else if (type instanceof GraphQLInputObjectField) { + return ((GraphQLInputObjectField) type).getType(); + } + return null; + }; + IntrospectionDataFetcher deprecationReasonDataFetcher = environment -> { + Object type = environment.getSource(); + if (type instanceof GraphQLArgument) { + return ((GraphQLArgument) type).getDeprecationReason(); + } else if (type instanceof GraphQLInputObjectField) { + return ((GraphQLInputObjectField) type).getDeprecationReason(); + } + return null; + }; + register(__InputValue, "name", nameDataFetcher); register(__InputValue, "description", descriptionDataFetcher); + register(__InputValue, "type", typeDataFetcher); + register(__InputValue, "defaultValue", defaultValueDataFetcher); + register(__InputValue, "isDeprecated", isDeprecatedDataFetcher); + register(__InputValue, "deprecationReason", deprecationReasonDataFetcher); } private static String printDefaultValue(InputValueWithState inputValueWithState, GraphQLInputType type, GraphQLContext graphQLContext, Locale locale) { @@ -220,20 +265,20 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .build(); static { - register(__Field, "args", environment -> { + IntrospectionDataFetcher argsDataFetcher = environment -> { Object type = environment.getSource(); GraphQLFieldDefinition fieldDef = (GraphQLFieldDefinition) type; Boolean includeDeprecated = environment.getArgument("includeDeprecated"); return fieldDef.getArguments().stream() .filter(arg -> includeDeprecated || !arg.isDeprecated()) .collect(Collectors.toList()); - }); - register(__Field, "isDeprecated", environment -> { - Object type = environment.getSource(); - return ((GraphQLFieldDefinition) type).isDeprecated(); - }); + }; register(__Field, "name", nameDataFetcher); register(__Field, "description", descriptionDataFetcher); + register(__Field, "args", argsDataFetcher); + register(__Field, "type", GraphQLFieldDefinition.class, GraphQLFieldDefinition::getType); + register(__Field, "isDeprecated", GraphQLFieldDefinition.class, GraphQLFieldDefinition::isDeprecated); + register(__Field, "deprecationReason", GraphQLFieldDefinition.class, GraphQLFieldDefinition::getDeprecationReason); } @@ -254,12 +299,10 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .build(); static { - register(__EnumValue, "isDeprecated", environment -> { - GraphQLEnumValueDefinition enumValue = environment.getSource(); - return enumValue.isDeprecated(); - }); register(__EnumValue, "name", nameDataFetcher); register(__EnumValue, "description", descriptionDataFetcher); + register(__EnumValue, "isDeprecated", GraphQLEnumValueDefinition.class, GraphQLEnumValueDefinition::isDeprecated); + register(__EnumValue, "deprecationReason", GraphQLEnumValueDefinition.class, GraphQLEnumValueDefinition::getDeprecationReason); } @@ -401,22 +444,22 @@ private static String printDefaultValue(InputValueWithState inputValueWithState, .name("specifiedByURL") .type(GraphQLString)) .field(newFieldDefinition() - .name("specifiedByUrl") - .type(GraphQLString) - .deprecate("see `specifiedByURL`") + .name("specifiedByUrl") + .type(GraphQLString) + .deprecate("This legacy name has been replaced by `specifiedByURL`") ) .build(); static { register(__Type, "kind", kindDataFetcher); + register(__Type, "name", nameDataFetcher); + register(__Type, "description", descriptionDataFetcher); register(__Type, "fields", fieldsFetcher); register(__Type, "interfaces", interfacesFetcher); register(__Type, "possibleTypes", possibleTypesFetcher); register(__Type, "enumValues", enumValuesTypesFetcher); register(__Type, "inputFields", inputFieldsFetcher); register(__Type, "ofType", OfTypeFetcher); - register(__Type, "name", nameDataFetcher); - register(__Type, "description", descriptionDataFetcher); register(__Type, "specifiedByURL", specifiedByUrlDataFetcher); register(__Type, "specifiedByUrl", specifiedByUrlDataFetcher); // note that this field is deprecated } @@ -497,38 +540,25 @@ public enum DirectiveLocation { .name("includeDeprecated") .type(GraphQLBoolean) .defaultValueProgrammatic(false))) - .field(newFieldDefinition() - .name("onOperation") - .type(GraphQLBoolean) - .deprecate("Use `locations`.")) - .field(newFieldDefinition() - .name("onFragment") - .type(GraphQLBoolean) - .deprecate("Use `locations`.")) - .field(newFieldDefinition() - .name("onField") - .type(GraphQLBoolean) - .deprecate("Use `locations`.")) .build(); static { - register(__Directive, "locations", environment -> { + IntrospectionDataFetcher locationsDataFetcher = environment -> { GraphQLDirective directive = environment.getSource(); return new ArrayList<>(directive.validLocations()); - }); - register(__Directive, "args", environment -> { + }; + IntrospectionDataFetcher argsDataFetcher = environment -> { GraphQLDirective directive = environment.getSource(); Boolean includeDeprecated = environment.getArgument("includeDeprecated"); return directive.getArguments().stream() .filter(arg -> includeDeprecated || !arg.isDeprecated()) .collect(Collectors.toList()); - }); + }; register(__Directive, "name", nameDataFetcher); register(__Directive, "description", descriptionDataFetcher); - register(__Directive, "isRepeatable", environment -> { - GraphQLDirective directive = environment.getSource(); - return directive.isRepeatable(); - }); + register(__Directive, "isRepeatable", GraphQLDirective.class, GraphQLDirective::isRepeatable); + register(__Directive, "locations", locationsDataFetcher); + register(__Directive, "args", argsDataFetcher); } public static final GraphQLObjectType __Schema = newObject() @@ -562,24 +592,14 @@ public enum DirectiveLocation { .build(); static { - register(__Schema, "description", environment -> environment.getGraphQLSchema().getDescription()); - register(__Schema, "types", environment -> { - GraphQLSchema schema = environment.getSource(); - return schema.getAllTypesAsList(); - }); - register(__Schema, "queryType", environment -> { - GraphQLSchema schema = environment.getSource(); - return schema.getQueryType(); - }); - register(__Schema, "mutationType", environment -> { - GraphQLSchema schema = environment.getSource(); - return schema.getMutationType(); - }); - register(__Schema, "directives", environment -> environment.getGraphQLSchema().getDirectives()); - register(__Schema, "subscriptionType", environment -> { - GraphQLSchema schema = environment.getSource(); - return schema.getSubscriptionType(); - }); + IntrospectionDataFetcher descriptionsDataFetcher = environment -> environment.getGraphQLSchema().getDescription(); + + register(__Schema, "description", descriptionsDataFetcher); + register(__Schema, "types", GraphQLSchema.class, GraphQLSchema::getAllTypesAsList); + register(__Schema, "queryType", GraphQLSchema.class, GraphQLSchema::getQueryType); + register(__Schema, "mutationType", GraphQLSchema.class, GraphQLSchema::getMutationType); + register(__Schema, "directives", GraphQLSchema.class, GraphQLSchema::getDirectives); + register(__Schema, "subscriptionType", GraphQLSchema.class, GraphQLSchema::getSubscriptionType); } public static final GraphQLFieldDefinition SchemaMetaFieldDef = buildSchemaField(__Schema); diff --git a/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java b/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java index ec6da5ee32..a533dd1e2d 100644 --- a/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java +++ b/src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java @@ -13,6 +13,7 @@ import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLDirectiveContainer; +import graphql.schema.GraphQLNamedSchemaElement; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLSchemaElement; @@ -207,8 +208,18 @@ private GraphQLObjectType addAppliedDirectives(GraphQLObjectType originalType, G Node literal = ValuesResolver.valueToLiteral(value, argument.getType(), env.getGraphQlContext(), env.getLocale()); return AstPrinter.printAst(literal); }; + DataFetcher nameDF = env -> { + if (env.getSource() instanceof GraphQLNamedSchemaElement) { + return ((GraphQLNamedSchemaElement) env.getSource()).getName(); + } + return null; + }; + codeRegistry.dataFetcher(coordinates(objectType, "appliedDirectives"), df); + codeRegistry.dataFetcher(coordinates(appliedDirectiveType, "name"), nameDF); codeRegistry.dataFetcher(coordinates(appliedDirectiveType, "args"), argsDF); + + codeRegistry.dataFetcher(coordinates(directiveArgumentType, "name"), nameDF); codeRegistry.dataFetcher(coordinates(directiveArgumentType, "value"), argValueDF); return objectType; } From d16414d5554286a52e51b3106eaa0af1925da72f Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 29 Nov 2022 17:08:05 +1000 Subject: [PATCH 279/294] mark everything as internal for now --- src/main/java/graphql/schema/diffing/Edge.java | 4 ++-- src/main/java/graphql/schema/diffing/EditOperation.java | 4 ++-- .../graphql/schema/diffing/FillupIsolatedVertices.java | 8 +++++--- src/main/java/graphql/schema/diffing/SchemaDiffing.java | 4 ++-- src/main/java/graphql/schema/diffing/Vertex.java | 5 ++--- .../schema/diffing/ana/EditOperationAnalysisResult.java | 4 ++-- .../graphql/schema/diffing/ana/EditOperationAnalyzer.java | 4 ++-- .../java/graphql/schema/diffing/ana/SchemaDifference.java | 4 ++-- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/Edge.java b/src/main/java/graphql/schema/diffing/Edge.java index b80a733472..15a603ef62 100644 --- a/src/main/java/graphql/schema/diffing/Edge.java +++ b/src/main/java/graphql/schema/diffing/Edge.java @@ -1,10 +1,10 @@ package graphql.schema.diffing; -import graphql.ExperimentalApi; +import graphql.Internal; import java.util.Objects; -@ExperimentalApi +@Internal public class Edge { private Vertex from; private Vertex to; diff --git a/src/main/java/graphql/schema/diffing/EditOperation.java b/src/main/java/graphql/schema/diffing/EditOperation.java index 4b5173f8c3..7ee0ef8adc 100644 --- a/src/main/java/graphql/schema/diffing/EditOperation.java +++ b/src/main/java/graphql/schema/diffing/EditOperation.java @@ -1,10 +1,10 @@ package graphql.schema.diffing; -import graphql.ExperimentalApi; +import graphql.Internal; import java.util.Objects; -@ExperimentalApi +@Internal public class EditOperation { private EditOperation(Operation operation, diff --git a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java index b08f07cc57..0f27e76ce7 100644 --- a/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java +++ b/src/main/java/graphql/schema/diffing/FillupIsolatedVertices.java @@ -8,6 +8,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Table; import graphql.Assert; +import graphql.Internal; import graphql.util.FpKit; import java.util.ArrayList; @@ -38,6 +39,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +@Internal public class FillupIsolatedVertices { SchemaGraph sourceGraph; @@ -105,7 +107,6 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { } - private static List scalarContext() { VertexContextSegment scalar = new VertexContextSegment() { @Override @@ -380,7 +381,7 @@ public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) { Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective); switch (container.getType()) { case SCHEMA: - return SCHEMA; + return SCHEMA; case FIELD: Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container); return fieldsContainer.getType() + "." + fieldsContainer.getName(); @@ -607,6 +608,7 @@ public boolean filter(Vertex vertex, SchemaGraph schemaGraph) { }; return singletonList(schema); } + private static List fieldContext() { VertexContextSegment field = new VertexContextSegment() { @Override @@ -784,7 +786,7 @@ public void addIsolatedTarget(Collection isolatedTarget) { allIsolatedTarget.addAll(isolatedTarget); } -// + // public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) { return possibleMappings.containsEntry(sourceVertex, targetVertex); } diff --git a/src/main/java/graphql/schema/diffing/SchemaDiffing.java b/src/main/java/graphql/schema/diffing/SchemaDiffing.java index f8d56f77cd..763ed0b1b3 100644 --- a/src/main/java/graphql/schema/diffing/SchemaDiffing.java +++ b/src/main/java/graphql/schema/diffing/SchemaDiffing.java @@ -1,6 +1,6 @@ package graphql.schema.diffing; -import graphql.ExperimentalApi; +import graphql.Internal; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.ana.EditOperationAnalysisResult; import graphql.schema.diffing.ana.EditOperationAnalyzer; @@ -13,7 +13,7 @@ import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; import static java.util.Collections.singletonList; -@ExperimentalApi +@Internal public class SchemaDiffing { diff --git a/src/main/java/graphql/schema/diffing/Vertex.java b/src/main/java/graphql/schema/diffing/Vertex.java index 5088bcee6b..0011f63fe8 100644 --- a/src/main/java/graphql/schema/diffing/Vertex.java +++ b/src/main/java/graphql/schema/diffing/Vertex.java @@ -1,16 +1,15 @@ package graphql.schema.diffing; import graphql.Assert; -import graphql.ExperimentalApi; +import graphql.Internal; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.Set; -@ExperimentalApi +@Internal public class Vertex { private String type; diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java index 1c37d0755e..d2eca0f7a4 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalysisResult.java @@ -1,10 +1,10 @@ package graphql.schema.diffing.ana; -import graphql.ExperimentalApi; +import graphql.Internal; import java.util.Map; -@ExperimentalApi +@Internal public class EditOperationAnalysisResult { private final Map objectDifferences; private final Map interfaceDifferences; diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index 0bd06dc768..d48a45b5d8 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -1,7 +1,7 @@ package graphql.schema.diffing.ana; import graphql.Assert; -import graphql.ExperimentalApi; +import graphql.Internal; import graphql.schema.GraphQLSchema; import graphql.schema.diffing.Edge; import graphql.schema.diffing.EditOperation; @@ -101,7 +101,7 @@ /** * Higher level GraphQL semantic assigned to */ -@ExperimentalApi +@Internal public class EditOperationAnalyzer { private GraphQLSchema oldSchema; diff --git a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java index 8fd9fd6b4b..6c4b1bcbf9 100644 --- a/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java +++ b/src/main/java/graphql/schema/diffing/ana/SchemaDifference.java @@ -1,6 +1,6 @@ package graphql.schema.diffing.ana; -import graphql.ExperimentalApi; +import graphql.Internal; import graphql.util.FpKit; import java.util.ArrayList; @@ -14,7 +14,7 @@ * - Deletion * - Modification */ -@ExperimentalApi +@Internal public interface SchemaDifference { interface SchemaAddition extends SchemaDifference { From 1d0fdbfb52bbfdcb308b3c1a53e3d38470561020 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 29 Nov 2022 17:48:10 +1000 Subject: [PATCH 280/294] improved test --- .../java/graphql/schema/diffing/SchemaGraph.java | 7 ++++--- .../schema/diffing/SchemaDiffingTest.groovy | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/diffing/SchemaGraph.java b/src/main/java/graphql/schema/diffing/SchemaGraph.java index d5ff50f310..eb64c3e4ef 100644 --- a/src/main/java/graphql/schema/diffing/SchemaGraph.java +++ b/src/main/java/graphql/schema/diffing/SchemaGraph.java @@ -5,12 +5,10 @@ import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Table; -import graphql.Assert; import graphql.ExperimentalApi; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -18,7 +16,6 @@ import java.util.Optional; import java.util.function.Predicate; -import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; import static java.lang.String.format; @@ -77,6 +74,10 @@ public Collection getVerticesByType(String type) { return typeToVertices.get(type); } + public Multimap getVerticesByType() { + return typeToVertices; + } + public void addEdge(Edge edge) { edges.add(edge); edgesByDirection.put(edge.getFrom(), edge.getTo(), edge); diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index a99481af15..259fa2f86d 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -26,6 +26,21 @@ class SchemaDiffingTest extends Specification { def schemaGraph = new SchemaGraphFactory().createGraph(schema) then: + schemaGraph.getVerticesByType().keySet().size() == 8 + schemaGraph.getVerticesByType(SchemaGraph.SCHEMA).size() == 1 + schemaGraph.getVerticesByType(SchemaGraph.OBJECT).size() == 7 + schemaGraph.getVerticesByType(SchemaGraph.ENUM).size() == 2 + schemaGraph.getVerticesByType(SchemaGraph.ENUM_VALUE).size() == 27 + schemaGraph.getVerticesByType(SchemaGraph.INTERFACE).size() == 0 + schemaGraph.getVerticesByType(SchemaGraph.UNION).size() == 0 + schemaGraph.getVerticesByType(SchemaGraph.SCALAR).size() == 2 + schemaGraph.getVerticesByType(SchemaGraph.FIELD).size() == 42 + schemaGraph.getVerticesByType(SchemaGraph.ARGUMENT).size() == 9 + schemaGraph.getVerticesByType(SchemaGraph.INPUT_FIELD).size() == 0 + schemaGraph.getVerticesByType(SchemaGraph.INPUT_OBJECT).size() == 0 + schemaGraph.getVerticesByType(SchemaGraph.DIRECTIVE).size() == 4 + schemaGraph.getVerticesByType(SchemaGraph.APPLIED_ARGUMENT).size() == 0 + schemaGraph.getVerticesByType(SchemaGraph.APPLIED_DIRECTIVE).size() == 0 schemaGraph.size() == 94 } From 0e4c2f3662c0f5803c8d71a0773abef6b0713613 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 29 Nov 2022 19:23:04 +1000 Subject: [PATCH 281/294] improved test --- src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 259fa2f86d..609fb6e97c 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -34,6 +34,7 @@ class SchemaDiffingTest extends Specification { schemaGraph.getVerticesByType(SchemaGraph.INTERFACE).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.UNION).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.SCALAR).size() == 2 + println schemaGraph.getVerticesByType(SchemaGraph.FIELD) schemaGraph.getVerticesByType(SchemaGraph.FIELD).size() == 42 schemaGraph.getVerticesByType(SchemaGraph.ARGUMENT).size() == 9 schemaGraph.getVerticesByType(SchemaGraph.INPUT_FIELD).size() == 0 From 08cdb586a2228afc4a3176466b28cbcd7e9f74de Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 29 Nov 2022 19:38:04 +1000 Subject: [PATCH 282/294] fix test --- .../groovy/graphql/schema/diffing/SchemaDiffingTest.groovy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 609fb6e97c..8f55d7bc12 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -34,15 +34,14 @@ class SchemaDiffingTest extends Specification { schemaGraph.getVerticesByType(SchemaGraph.INTERFACE).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.UNION).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.SCALAR).size() == 2 - println schemaGraph.getVerticesByType(SchemaGraph.FIELD) - schemaGraph.getVerticesByType(SchemaGraph.FIELD).size() == 42 + schemaGraph.getVerticesByType(SchemaGraph.FIELD).size() == 39 schemaGraph.getVerticesByType(SchemaGraph.ARGUMENT).size() == 9 schemaGraph.getVerticesByType(SchemaGraph.INPUT_FIELD).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.INPUT_OBJECT).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.DIRECTIVE).size() == 4 schemaGraph.getVerticesByType(SchemaGraph.APPLIED_ARGUMENT).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.APPLIED_DIRECTIVE).size() == 0 - schemaGraph.size() == 94 + schemaGraph.size() == 91 } From b424ab88a6031932ba12c5944cd2de44b2e8a7e8 Mon Sep 17 00:00:00 2001 From: Federico Rispo <44871614+federicorispo@users.noreply.github.com> Date: Wed, 30 Nov 2022 09:51:19 +0100 Subject: [PATCH 283/294] chore: Remove unused imports and local variables (#3034) All classes are now free of unused imports and unused local variables. In SchemaGeneratorHelper: replaced the use of the bitwise operator with the || operator. --- src/main/java/graphql/Directives.java | 3 --- src/main/java/graphql/ExecutionResultImpl.java | 1 - src/main/java/graphql/cachecontrol/CacheControl.java | 1 - .../java/graphql/execution/AsyncExecutionStrategy.java | 2 -- src/main/java/graphql/i18n/I18n.java | 1 - src/main/java/graphql/language/NodeBuilder.java | 2 -- .../language/ObjectTypeExtensionDefinition.java | 1 - src/main/java/graphql/language/PrettyAstPrinter.java | 1 - src/main/java/graphql/language/TypeName.java | 1 - src/main/java/graphql/language/VariableReference.java | 1 - src/main/java/graphql/schema/CodeRegistryVisitor.java | 10 ---------- .../schema/GraphQLAppliedDirectiveArgument.java | 1 - src/main/java/graphql/schema/diff/DiffCtx.java | 1 - src/main/java/graphql/schema/diffing/DiffImpl.java | 6 ++---- .../schema/diffing/ana/EditOperationAnalyzer.java | 3 +-- .../java/graphql/schema/idl/SchemaGeneratorHelper.java | 2 +- .../schema/impl/GraphQLTypeCollectingVisitor.java | 2 -- .../graphql/schema/validation/TypeAndFieldRule.java | 1 - src/main/java/graphql/util/Anonymizer.java | 3 --- .../java/graphql/util/TreeParallelTransformer.java | 1 - src/main/java/graphql/util/TreeTransformer.java | 1 - src/main/java/graphql/validation/TraversalContext.java | 1 - .../graphql/validation/rules/KnownArgumentNames.java | 1 - .../graphql/validation/rules/NoUnusedVariables.java | 1 - 24 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/main/java/graphql/Directives.java b/src/main/java/graphql/Directives.java index 7c993d071c..caff79a7d5 100644 --- a/src/main/java/graphql/Directives.java +++ b/src/main/java/graphql/Directives.java @@ -1,14 +1,11 @@ package graphql; -import com.google.common.collect.ImmutableSet; import graphql.language.Description; import graphql.language.DirectiveDefinition; import graphql.language.StringValue; import graphql.schema.GraphQLDirective; -import java.util.Set; - import static graphql.Scalars.GraphQLBoolean; import static graphql.Scalars.GraphQLString; import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; diff --git a/src/main/java/graphql/ExecutionResultImpl.java b/src/main/java/graphql/ExecutionResultImpl.java index bec1b5ac4c..33ddd67e21 100644 --- a/src/main/java/graphql/ExecutionResultImpl.java +++ b/src/main/java/graphql/ExecutionResultImpl.java @@ -9,7 +9,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import static graphql.collect.ImmutableKit.map; diff --git a/src/main/java/graphql/cachecontrol/CacheControl.java b/src/main/java/graphql/cachecontrol/CacheControl.java index 7dc662ba5a..7b12b2fb0d 100644 --- a/src/main/java/graphql/cachecontrol/CacheControl.java +++ b/src/main/java/graphql/cachecontrol/CacheControl.java @@ -3,7 +3,6 @@ import graphql.DeprecatedAt; import graphql.ExecutionInput; import graphql.ExecutionResult; -import graphql.ExecutionResultImpl; import graphql.PublicApi; import graphql.execution.ResultPath; import graphql.schema.DataFetchingEnvironment; diff --git a/src/main/java/graphql/execution/AsyncExecutionStrategy.java b/src/main/java/graphql/execution/AsyncExecutionStrategy.java index 025505188f..fdebcb6cca 100644 --- a/src/main/java/graphql/execution/AsyncExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncExecutionStrategy.java @@ -6,9 +6,7 @@ import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; -import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; diff --git a/src/main/java/graphql/i18n/I18n.java b/src/main/java/graphql/i18n/I18n.java index 7e29de75b4..b401231166 100644 --- a/src/main/java/graphql/i18n/I18n.java +++ b/src/main/java/graphql/i18n/I18n.java @@ -1,6 +1,5 @@ package graphql.i18n; -import graphql.GraphQLContext; import graphql.Internal; import graphql.VisibleForTesting; diff --git a/src/main/java/graphql/language/NodeBuilder.java b/src/main/java/graphql/language/NodeBuilder.java index 878358d874..df88e16632 100644 --- a/src/main/java/graphql/language/NodeBuilder.java +++ b/src/main/java/graphql/language/NodeBuilder.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.Map; -import static graphql.Assert.assertNotNull; - @PublicApi public interface NodeBuilder { diff --git a/src/main/java/graphql/language/ObjectTypeExtensionDefinition.java b/src/main/java/graphql/language/ObjectTypeExtensionDefinition.java index 400f70054e..575827564f 100644 --- a/src/main/java/graphql/language/ObjectTypeExtensionDefinition.java +++ b/src/main/java/graphql/language/ObjectTypeExtensionDefinition.java @@ -6,7 +6,6 @@ import graphql.PublicApi; import graphql.collect.ImmutableKit; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/language/PrettyAstPrinter.java b/src/main/java/graphql/language/PrettyAstPrinter.java index eb02039f29..02a06bf004 100644 --- a/src/main/java/graphql/language/PrettyAstPrinter.java +++ b/src/main/java/graphql/language/PrettyAstPrinter.java @@ -1,7 +1,6 @@ package graphql.language; import graphql.ExperimentalApi; -import graphql.PublicApi; import graphql.collect.ImmutableKit; import graphql.parser.CommentParser; import graphql.parser.NodeToRuleCapturingParser; diff --git a/src/main/java/graphql/language/TypeName.java b/src/main/java/graphql/language/TypeName.java index 313a0b9abb..add06add41 100644 --- a/src/main/java/graphql/language/TypeName.java +++ b/src/main/java/graphql/language/TypeName.java @@ -7,7 +7,6 @@ import graphql.util.TraversalControl; import graphql.util.TraverserContext; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/language/VariableReference.java b/src/main/java/graphql/language/VariableReference.java index 14555402bb..ac085c694b 100644 --- a/src/main/java/graphql/language/VariableReference.java +++ b/src/main/java/graphql/language/VariableReference.java @@ -7,7 +7,6 @@ import graphql.util.TraversalControl; import graphql.util.TraverserContext; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/schema/CodeRegistryVisitor.java b/src/main/java/graphql/schema/CodeRegistryVisitor.java index 66af22f209..50cfa2a492 100644 --- a/src/main/java/graphql/schema/CodeRegistryVisitor.java +++ b/src/main/java/graphql/schema/CodeRegistryVisitor.java @@ -2,16 +2,6 @@ import graphql.Internal; import graphql.introspection.Introspection; -import graphql.schema.DataFetcher; -import graphql.schema.FieldCoordinates; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLFieldsContainer; -import graphql.schema.GraphQLInterfaceType; -import graphql.schema.GraphQLSchemaElement; -import graphql.schema.GraphQLTypeVisitorStub; -import graphql.schema.GraphQLUnionType; -import graphql.schema.TypeResolver; import graphql.util.TraversalControl; import graphql.util.TraverserContext; diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java index 276e4f4102..81172a3721 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java @@ -4,7 +4,6 @@ import graphql.Assert; import graphql.GraphQLContext; import graphql.PublicApi; -import graphql.collect.ImmutableKit; import graphql.language.Argument; import graphql.language.Value; import graphql.util.TraversalControl; diff --git a/src/main/java/graphql/schema/diff/DiffCtx.java b/src/main/java/graphql/schema/diff/DiffCtx.java index 048685d0c3..975189c174 100644 --- a/src/main/java/graphql/schema/diff/DiffCtx.java +++ b/src/main/java/graphql/schema/diff/DiffCtx.java @@ -11,7 +11,6 @@ import java.util.Deque; import java.util.List; import java.util.Optional; -import java.util.Stack; /* * A helper class that represents diff state (eg visited types) as well as helpers diff --git a/src/main/java/graphql/schema/diffing/DiffImpl.java b/src/main/java/graphql/schema/diffing/DiffImpl.java index 2db9b1d0f1..ee8c847f0d 100644 --- a/src/main/java/graphql/schema/diffing/DiffImpl.java +++ b/src/main/java/graphql/schema/diffing/DiffImpl.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -19,7 +18,6 @@ import static graphql.Assert.assertTrue; import static graphql.schema.diffing.EditorialCostForMapping.editorialCostForMapping; -import static java.util.Collections.singletonList; @Internal public class DiffImpl { @@ -97,7 +95,7 @@ OptimalEdit diffImpl(Mapping startMapping, List relevantSourceList, List queue.add(firstMappingEntry); firstMappingEntry.siblingsFinished = true; // queue.add(new MappingEntry()); - int counter = 0; +// int counter = 0; while (!queue.isEmpty()) { MappingEntry mappingEntry = queue.poll(); // System.out.println((++counter) + " check entry at level " + mappingEntry.level + " queue size: " + queue.size() + " lower bound " + mappingEntry.lowerBoundCost + " map " + getDebugMap(mappingEntry.partialMapping)); @@ -136,7 +134,7 @@ private void addChildToQueue(MappingEntry parentEntry, List sourceList, List targetList - ) throws Exception { + ) { Mapping partialMapping = parentEntry.partialMapping; int level = parentEntry.level; diff --git a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java index d48a45b5d8..9fcc3ce154 100644 --- a/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java +++ b/src/main/java/graphql/schema/diffing/ana/EditOperationAnalyzer.java @@ -625,7 +625,7 @@ private void handleInputFieldChange(EditOperation editOperation) { Vertex inputObject = newSchemaGraph.getInputObjectForInputField(inputField); String oldName = editOperation.getSourceVertex().getName(); String newName = inputObject.getName(); - getInputObjectModification(inputObject.getName()).getDetails().add(new InputObjectFieldRename(oldName, inputField.getName())); + getInputObjectModification(newName).getDetails().add(new InputObjectFieldRename(oldName, inputField.getName())); } private void handleArgumentChange(EditOperation editOperation) { @@ -1068,7 +1068,6 @@ private EditOperation findDeletedEdge(Vertex targetVertexFrom, List directivesOf(List> typeDefin private T directivesObserve(BuildContext buildCtx, T directiveContainer) { if (!buildCtx.directiveWiringRequired) { boolean requiresWiring = SchemaGeneratorDirectiveHelper.schemaDirectiveWiringIsRequired(directiveContainer, buildCtx.getTypeRegistry(), buildCtx.getWiring()); - buildCtx.directiveWiringRequired = buildCtx.directiveWiringRequired | requiresWiring; + buildCtx.directiveWiringRequired = buildCtx.directiveWiringRequired || requiresWiring; } return directiveContainer; } diff --git a/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java index 79116a6df8..e6ceb76e3c 100644 --- a/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java +++ b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java @@ -10,9 +10,7 @@ import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInterfaceType; -import graphql.schema.GraphQLList; import graphql.schema.GraphQLNamedType; -import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchemaElement; diff --git a/src/main/java/graphql/schema/validation/TypeAndFieldRule.java b/src/main/java/graphql/schema/validation/TypeAndFieldRule.java index 4cb8d3a292..84a768479d 100644 --- a/src/main/java/graphql/schema/validation/TypeAndFieldRule.java +++ b/src/main/java/graphql/schema/validation/TypeAndFieldRule.java @@ -23,7 +23,6 @@ import graphql.util.TraversalControl; import graphql.util.TraverserContext; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index f2614ef02b..34553d8777 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -43,7 +43,6 @@ import graphql.language.VariableDefinition; import graphql.language.VariableReference; import graphql.parser.Parser; -import graphql.parser.ParserEnvironment; import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLArgument; @@ -798,8 +797,6 @@ public TraversalControl visitArgument(QueryVisitorFieldArgumentEnvironment envir } }); - AtomicInteger stringValueCounter = new AtomicInteger(1); - AtomicInteger intValueCounter = new AtomicInteger(1); AstTransformer astTransformer = new AstTransformer(); AtomicInteger aliasCounter = new AtomicInteger(1); AtomicInteger defaultStringValueCounter = new AtomicInteger(1); diff --git a/src/main/java/graphql/util/TreeParallelTransformer.java b/src/main/java/graphql/util/TreeParallelTransformer.java index d59151dd94..0102af2eff 100644 --- a/src/main/java/graphql/util/TreeParallelTransformer.java +++ b/src/main/java/graphql/util/TreeParallelTransformer.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; diff --git a/src/main/java/graphql/util/TreeTransformer.java b/src/main/java/graphql/util/TreeTransformer.java index ebfc9cc950..970b0eac48 100644 --- a/src/main/java/graphql/util/TreeTransformer.java +++ b/src/main/java/graphql/util/TreeTransformer.java @@ -4,7 +4,6 @@ import graphql.PublicApi; import graphql.collect.ImmutableKit; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; diff --git a/src/main/java/graphql/validation/TraversalContext.java b/src/main/java/graphql/validation/TraversalContext.java index f9a157941e..dfd3e4920d 100644 --- a/src/main/java/graphql/validation/TraversalContext.java +++ b/src/main/java/graphql/validation/TraversalContext.java @@ -2,7 +2,6 @@ import graphql.Assert; -import graphql.DirectivesUtil; import graphql.Internal; import graphql.execution.TypeFromAST; import graphql.language.Argument; diff --git a/src/main/java/graphql/validation/rules/KnownArgumentNames.java b/src/main/java/graphql/validation/rules/KnownArgumentNames.java index df0348e900..8f123f7412 100644 --- a/src/main/java/graphql/validation/rules/KnownArgumentNames.java +++ b/src/main/java/graphql/validation/rules/KnownArgumentNames.java @@ -8,7 +8,6 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import static graphql.validation.ValidationErrorType.UnknownArgument; import static graphql.validation.ValidationErrorType.UnknownDirective; diff --git a/src/main/java/graphql/validation/rules/NoUnusedVariables.java b/src/main/java/graphql/validation/rules/NoUnusedVariables.java index 165fa2092c..80e3aa9429 100644 --- a/src/main/java/graphql/validation/rules/NoUnusedVariables.java +++ b/src/main/java/graphql/validation/rules/NoUnusedVariables.java @@ -8,7 +8,6 @@ import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; import graphql.validation.ValidationErrorCollector; -import graphql.validation.ValidationErrorType; import java.util.ArrayList; import java.util.LinkedHashSet; From fe8d6705459b8abb997a45d436eef8b324218bff Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Thu, 1 Dec 2022 10:59:50 +1100 Subject: [PATCH 284/294] Testing I18n lookup (#3036) * Testing I18n lookup * Testing I18n lookup - an integration test * Testing I18n lookup - an integration test - with German test case --- src/test/groovy/graphql/i18n/I18nTest.groovy | 92 ++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/test/groovy/graphql/i18n/I18nTest.groovy b/src/test/groovy/graphql/i18n/I18nTest.groovy index ab02791572..84165264e7 100644 --- a/src/test/groovy/graphql/i18n/I18nTest.groovy +++ b/src/test/groovy/graphql/i18n/I18nTest.groovy @@ -1,6 +1,8 @@ package graphql.i18n import graphql.AssertException +import graphql.ExecutionInput +import graphql.TestUtil import graphql.i18n.I18n.BundleType import spock.lang.Specification @@ -14,6 +16,43 @@ class I18nTest extends Specification { thrown(AssertException) } + def "missing resource bundles default to a base version"() { + // see https://saimana.com/list-of-country-locale-code/ + + def expected = "Validation error ({0}) : Type '{1}' definition is not executable" + + when: + def i18n = I18n.i18n(BundleType.Validation, Locale.ENGLISH) + def msg = i18n.msg("ExecutableDefinitions.notExecutableType") + + then: + msg == expected + + when: + i18n = I18n.i18n(BundleType.Validation, Locale.CHINESE) + msg = i18n.msg("ExecutableDefinitions.notExecutableType") + then: + msg == expected + + when: + i18n = I18n.i18n(BundleType.Validation, new Locale("en", "IN")) // India + msg = i18n.msg("ExecutableDefinitions.notExecutableType") + then: + msg == expected + + when: + i18n = I18n.i18n(BundleType.Validation, new Locale("en", "FJ")) // Fiji + msg = i18n.msg("ExecutableDefinitions.notExecutableType") + then: + msg == expected + + when: + i18n = I18n.i18n(BundleType.Validation, new Locale("")) // Nothing + msg = i18n.msg("ExecutableDefinitions.notExecutableType") + then: + msg == expected + } + def "all enums have resources and decent shapes"() { when: def bundleTypes = BundleType.values() @@ -34,6 +73,59 @@ class I18nTest extends Specification { message == "Validierungsfehler ({0}) : Type definition '{1}' ist nicht ausführbar" } + def "integration test of valid messages"() { + def sdl = """ + type Query { + field(arg : Int) : Subselection + } + + type Subselection { + name : String + } + """ + def graphQL = TestUtil.graphQL(sdl).build() + + + when: + def locale = new Locale("en", "IN") + def ei = ExecutionInput.newExecutionInput().query("query missingSubselectionQ { field(arg : 1) }") + .locale(locale) + .build() + def er = graphQL.execute(ei) + then: + !er.errors.isEmpty() + er.errors[0].message == "Validation error (SubselectionRequired@[field]) : Subselection required for type 'Subselection' of field 'field'" + + when: + locale = Locale.GERMANY + ei = ExecutionInput.newExecutionInput().query("query missingSubselectionQ { field(arg : 1) }") + .locale(locale) + .build() + er = graphQL.execute(ei) + then: + !er.errors.isEmpty() + er.errors[0].message == "Validierungsfehler (SubselectionRequired@[field]) : Unterauswahl erforderlich für Typ 'Subselection' des Feldes 'field'" + + when: + locale = Locale.getDefault() + ei = ExecutionInput.newExecutionInput().query("query missingSubselectionQ { field(arg : 1) }") + .locale(locale) + .build() + er = graphQL.execute(ei) + then: + !er.errors.isEmpty() + er.errors[0].message == "Validation error (SubselectionRequired@[field]) : Subselection required for type 'Subselection' of field 'field'" + + when: + // no locale - it should default + ei = ExecutionInput.newExecutionInput().query("query missingSubselectionQ { field(arg : 1) }") + .build() + er = graphQL.execute(ei) + then: + !er.errors.isEmpty() + er.errors[0].message == "Validation error (SubselectionRequired@[field]) : Subselection required for type 'Subselection' of field 'field'" + } + static def assertBundleStaticShape(ResourceBundle bundle) { def enumeration = bundle.getKeys() while (enumeration.hasMoreElements()) { From 5b4421988c5b4e4c85c10a8b3fecebfe749a0311 Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Thu, 1 Dec 2022 12:53:17 +1100 Subject: [PATCH 285/294] Consider union containers when checking if type is referenced (#3037) Co-authored-by: Felipe Reis --- src/main/java/graphql/schema/usage/SchemaUsage.java | 12 ++++++++++++ .../graphql/schema/usage/SchemaUsageSupport.java | 1 + .../schema/usage/SchemaUsageSupportTest.groovy | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/main/java/graphql/schema/usage/SchemaUsage.java b/src/main/java/graphql/schema/usage/SchemaUsage.java index 0caff84459..25ed7fb24c 100644 --- a/src/main/java/graphql/schema/usage/SchemaUsage.java +++ b/src/main/java/graphql/schema/usage/SchemaUsage.java @@ -47,6 +47,8 @@ public class SchemaUsage { private final Map> interfaceImplementors; private final Map> elementBackReferences; + private final Map> unionReferences; + private SchemaUsage(Builder builder) { this.fieldReferenceCounts = ImmutableMap.copyOf(builder.fieldReferenceCounts); this.inputFieldReferenceCounts = ImmutableMap.copyOf(builder.inputFieldReferenceCounts); @@ -57,6 +59,7 @@ private SchemaUsage(Builder builder) { this.directiveReferenceCount = ImmutableMap.copyOf(builder.directiveReferenceCount); this.interfaceImplementors = ImmutableMap.copyOf(builder.interfaceImplementors); this.elementBackReferences = ImmutableMap.copyOf(builder.elementBackReferences); + this.unionReferences = ImmutableMap.copyOf(builder.unionReferences); } /** @@ -225,6 +228,13 @@ private boolean isReferencedImpl(GraphQLSchema schema, String elementName, Set unionContainers = unionReferences.getOrDefault(type.getName(), emptySet()); + for (String unionContainer : unionContainers) { + if (isReferencedImpl(schema, unionContainer, pathSoFar)) { + return true; + } + } } return false; } @@ -249,6 +259,8 @@ static class Builder { Map unionReferenceCount = new LinkedHashMap<>(); Map directiveReferenceCount = new LinkedHashMap<>(); Map> interfaceImplementors = new LinkedHashMap<>(); + + Map> unionReferences = new LinkedHashMap<>(); Map> elementBackReferences = new LinkedHashMap<>(); SchemaUsage build() { diff --git a/src/main/java/graphql/schema/usage/SchemaUsageSupport.java b/src/main/java/graphql/schema/usage/SchemaUsageSupport.java index cb41f69992..e540534d3b 100644 --- a/src/main/java/graphql/schema/usage/SchemaUsageSupport.java +++ b/src/main/java/graphql/schema/usage/SchemaUsageSupport.java @@ -135,6 +135,7 @@ public TraversalControl visitGraphQLUnionType(GraphQLUnionType unionType, Traver List members = unionType.getTypes(); for (GraphQLNamedOutputType member : members) { builder.unionReferenceCount.compute(member.getName(), incCount()); + builder.unionReferences.computeIfAbsent(member.getName(), k -> new HashSet<>()).add(unionType.getName()); recordBackReference(unionType, member); } diff --git a/src/test/groovy/graphql/schema/usage/SchemaUsageSupportTest.groovy b/src/test/groovy/graphql/schema/usage/SchemaUsageSupportTest.groovy index 091ddea02d..f0cff6c7fc 100644 --- a/src/test/groovy/graphql/schema/usage/SchemaUsageSupportTest.groovy +++ b/src/test/groovy/graphql/schema/usage/SchemaUsageSupportTest.groovy @@ -16,6 +16,7 @@ class SchemaUsageSupportTest extends Specification { f3 : RefUnion1 f4 : RefEnum1 f5 : String + f6 : RefUnion2 f_arg1( arg : RefInput1) : String f_arg2( arg : [RefInput2]) : String @@ -53,6 +54,12 @@ class SchemaUsageSupportTest extends Specification { union RefUnion1 = Ref1 | Ref2 + + type RefByUnionOnly1 { f : ID} + type RefByUnionOnly2 { f : ID} + + union RefUnion2 = RefByUnionOnly1 | RefByUnionOnly2 + enum RefEnum1 { A, B } @@ -154,6 +161,10 @@ class SchemaUsageSupportTest extends Specification { schemaUsage.isStronglyReferenced(schema, "RefUnion1") + schemaUsage.isStronglyReferenced(schema, "RefUnion2") + schemaUsage.isStronglyReferenced(schema, "RefByUnionOnly1") + schemaUsage.isStronglyReferenced(schema, "RefByUnionOnly2") + schemaUsage.isStronglyReferenced(schema, "RefInput1") schemaUsage.isStronglyReferenced(schema, "RefInput2") schemaUsage.isStronglyReferenced(schema, "RefInput3") From 3d452436595221700dd3795693f11fdea53c8b9d Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Fri, 2 Dec 2022 16:15:38 +1100 Subject: [PATCH 286/294] use class loader on master (#3039) --- src/main/java/graphql/i18n/I18n.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/i18n/I18n.java b/src/main/java/graphql/i18n/I18n.java index b401231166..6898f7be8b 100644 --- a/src/main/java/graphql/i18n/I18n.java +++ b/src/main/java/graphql/i18n/I18n.java @@ -41,8 +41,9 @@ protected I18n(BundleType bundleType, Locale locale) { assertNotNull(bundleType); assertNotNull(locale); this.locale = locale; - this.resourceBundle = ResourceBundle.getBundle(bundleType.baseName, locale); - } + // load the resource bundle with this classes class loader - to help avoid confusion in complicated worlds + // like OSGI + this.resourceBundle = ResourceBundle.getBundle(bundleType.baseName, locale, I18n.class.getClassLoader()); } public Locale getLocale() { return locale; From 476632e85474aa0720362ac1b1bc3e50ff4d04c1 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sat, 3 Dec 2022 13:31:49 +1100 Subject: [PATCH 287/294] Added a ValueTraverser allowing you to visit and optionally change input argument values (#3033) * Added a ValueTraverser allowing you to visit and optionally change input argument values * Added integration test * Added PR feedback - sentinals, new types and so on * PR tweaks * PR feedback * PR feedback 2 * More PR feedback from Andi - handled null object fields --- .../analysis/values/ValueTraverser.java | 273 ++++++ .../graphql/analysis/values/ValueVisitor.java | 125 +++ .../schema/GraphQLInputSchemaElement.java | 10 + .../java/graphql/schema/GraphQLInputType.java | 2 +- .../schema/GraphQLInputValueDefinition.java | 2 +- .../analysis/values/ValueTraverserTest.groovy | 793 ++++++++++++++++++ 6 files changed, 1203 insertions(+), 2 deletions(-) create mode 100644 src/main/java/graphql/analysis/values/ValueTraverser.java create mode 100644 src/main/java/graphql/analysis/values/ValueVisitor.java create mode 100644 src/main/java/graphql/schema/GraphQLInputSchemaElement.java create mode 100644 src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy diff --git a/src/main/java/graphql/analysis/values/ValueTraverser.java b/src/main/java/graphql/analysis/values/ValueTraverser.java new file mode 100644 index 0000000000..162926a5f3 --- /dev/null +++ b/src/main/java/graphql/analysis/values/ValueTraverser.java @@ -0,0 +1,273 @@ +package graphql.analysis.values; + +import com.google.common.collect.ImmutableList; +import graphql.Assert; +import graphql.PublicApi; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.DataFetchingEnvironmentImpl; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputSchemaElement; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLInputValueDefinition; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLTypeUtil; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static graphql.analysis.values.ValueVisitor.ABSENCE_SENTINEL; + +/** + * This class allows you to traverse a set of input values according to the type system and optional + * change the values present. + *

+ * If you just want to traverse without changing anything, just return the value presented to you and nothing will change. + *

+ * If you want to change a value, perhaps in the presence of a directive say on the containing element, then return + * a new value back in your visitor. + *

+ * This class is intended to be used say inside a DataFetcher, allowing you to change the {@link DataFetchingEnvironment#getArguments()} + * say before further processing. + *

+ * The values passed in are assumed to be valid and coerced. This classes does not check for non nullness say or the right coerced objects given + * the type system. This is assumed to have occurred earlier in the graphql validation phase. This also means if you are not careful you can undo the + * validation that has gone before you. For example, it would be possible to change values that are illegal according to the type system, such as + * null values for non-nullable types say, so you need to be careful. + */ +@PublicApi +public class ValueTraverser { + + private static class InputElements implements ValueVisitor.InputElements { + + private final ImmutableList inputElements; + private final List unwrappedInputElements; + private final GraphQLInputValueDefinition lastElement; + + private InputElements(GraphQLInputValueDefinition startElement) { + this.inputElements = ImmutableList.of(startElement); + this.unwrappedInputElements = ImmutableList.of(startElement); + this.lastElement = startElement; + } + + private InputElements(ImmutableList inputElements) { + this.inputElements = inputElements; + this.unwrappedInputElements = inputElements.stream() + .filter(it -> !(it instanceof GraphQLNonNull || it instanceof GraphQLList)) + .collect(ImmutableList.toImmutableList()); + + List inputValDefs = unwrappedInputElements.stream() + .filter(it -> it instanceof GraphQLInputValueDefinition) + .map(GraphQLInputValueDefinition.class::cast).collect(Collectors.toList()); + this.lastElement = inputValDefs.get(inputValDefs.size() - 1); + } + + + private InputElements push(GraphQLInputSchemaElement inputElement) { + ImmutableList newSchemaElements = ImmutableList.builder() + .addAll(inputElements).add(inputElement).build(); + return new InputElements(newSchemaElements); + } + + @Override + public List getInputElements() { + return inputElements; + } + + public List getUnwrappedInputElements() { + return unwrappedInputElements; + } + + @Override + public GraphQLInputValueDefinition getLastInputValueDefinition() { + return lastElement; + } + } + + /** + * This will visit the arguments of a {@link DataFetchingEnvironment} and if the values are changed by the visitor a new environment will be built + * + * @param environment the starting data fetching environment + * @param visitor the visitor to use + * + * @return the same environment if nothing changes or a new one with the {@link DataFetchingEnvironment#getArguments()} changed + */ + public static DataFetchingEnvironment visitPreOrder(DataFetchingEnvironment environment, ValueVisitor visitor) { + GraphQLFieldDefinition fieldDefinition = environment.getFieldDefinition(); + Map originalArgs = environment.getArguments(); + Map newArgs = visitPreOrder(originalArgs, fieldDefinition, visitor); + if (newArgs != originalArgs) { + return DataFetchingEnvironmentImpl.newDataFetchingEnvironment(environment).arguments(newArgs).build(); + } + return environment; + } + + /** + * This will visit the arguments of a {@link GraphQLFieldDefinition} and if the visitor changes the values, it will return a new set of arguments + * + * @param coercedArgumentValues the starting coerced arguments + * @param fieldDefinition the field definition + * @param visitor the visitor to use + * + * @return the same set of arguments if nothing changes or new ones if the visitor changes anything + */ + public static Map visitPreOrder(Map coercedArgumentValues, GraphQLFieldDefinition fieldDefinition, ValueVisitor visitor) { + List fieldArguments = fieldDefinition.getArguments(); + boolean copied = false; + for (GraphQLArgument fieldArgument : fieldArguments) { + String key = fieldArgument.getName(); + Object argValue = coercedArgumentValues.get(key); + InputElements inputElements = new InputElements(fieldArgument); + Object newValue = visitPreOrderImpl(argValue, fieldArgument.getType(), inputElements, visitor); + if (hasChanged(newValue, argValue)) { + if (!copied) { + coercedArgumentValues = new LinkedHashMap<>(coercedArgumentValues); + copied = true; + } + setNewValue(coercedArgumentValues, key, newValue); + } + } + return coercedArgumentValues; + } + + /** + * This will visit a single argument of a {@link GraphQLArgument} and if the visitor changes the value, it will return a new argument + * + * @param coercedArgumentValue the starting coerced argument value + * @param argument the argument definition + * @param visitor the visitor to use + * + * @return the same value if nothing changes or a new value if the visitor changes anything + */ + public static Object visitPreOrder(Object coercedArgumentValue, GraphQLArgument argument, ValueVisitor visitor) { + return visitPreOrderImpl(coercedArgumentValue, argument.getType(), new InputElements(argument), visitor); + } + + private static Object visitPreOrderImpl(Object coercedValue, GraphQLInputType startingInputType, InputElements containingElements, ValueVisitor visitor) { + if (startingInputType instanceof GraphQLNonNull) { + containingElements = containingElements.push(startingInputType); + } + GraphQLInputType inputType = GraphQLTypeUtil.unwrapNonNullAs(startingInputType); + containingElements = containingElements.push(inputType); + if (inputType instanceof GraphQLList) { + return visitListValue(coercedValue, (GraphQLList) inputType, containingElements, visitor); + } else if (inputType instanceof GraphQLInputObjectType) { + GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) inputType; + return visitObjectValue(coercedValue, inputObjectType, containingElements, visitor); + } else if (inputType instanceof GraphQLScalarType) { + return visitor.visitScalarValue(coercedValue, (GraphQLScalarType) inputType, containingElements); + } else if (inputType instanceof GraphQLEnumType) { + return visitor.visitEnumValue(coercedValue, (GraphQLEnumType) inputType, containingElements); + } else { + return Assert.assertShouldNeverHappen("ValueTraverser can only be called on full materialised schemas"); + } + } + + private static Object visitObjectValue(Object coercedValue, GraphQLInputObjectType inputObjectType, InputElements containingElements, ValueVisitor visitor) { + if (coercedValue != null) { + Assert.assertTrue(coercedValue instanceof Map, () -> "A input object type MUST have an Map value"); + } + @SuppressWarnings("unchecked") + Map map = (Map) coercedValue; + Map newMap = visitor.visitInputObjectValue(map, inputObjectType, containingElements); + if (newMap == ABSENCE_SENTINEL) { + return ABSENCE_SENTINEL; + } + if (newMap != null) { + boolean copied = false; + for (Map.Entry entry : newMap.entrySet()) { + String key = entry.getKey(); + GraphQLInputObjectField inputField = inputObjectType.getField(key); + /// should we assert if the map contain a key that's not a field ? + if (inputField != null) { + InputElements inputElementsWithField = containingElements.push(inputField); + Object newValue = visitor.visitInputObjectFieldValue(entry.getValue(), inputObjectType, inputField, inputElementsWithField); + if (hasChanged(newValue, entry.getValue())) { + if (!copied) { + newMap = new LinkedHashMap<>(newMap); + copied = true; + } + setNewValue(newMap, key, newValue); + } + // if the value has gone - then we cant descend into it + if (newValue != ABSENCE_SENTINEL) { + newValue = visitPreOrderImpl(newValue, inputField.getType(), inputElementsWithField, visitor); + if (hasChanged(newValue, entry.getValue())) { + if (!copied) { + newMap = new LinkedHashMap<>(newMap); + copied = true; + } + setNewValue(newMap, key, newValue); + } + } + } + } + return newMap; + } else { + return null; + } + } + + private static Object visitListValue(Object coercedValue, GraphQLList listInputType, InputElements containingElements, ValueVisitor visitor) { + if (coercedValue != null) { + Assert.assertTrue(coercedValue instanceof List, () -> "A list type MUST have an List value"); + } + @SuppressWarnings("unchecked") + List list = (List) coercedValue; + List newList = visitor.visitListValue(list, listInputType, containingElements); + if (newList == ABSENCE_SENTINEL) { + return ABSENCE_SENTINEL; + } + if (newList != null) { + GraphQLInputType inputType = GraphQLTypeUtil.unwrapOneAs(listInputType); + ImmutableList.Builder copiedList = null; + int i = 0; + for (Object subValue : newList) { + Object newValue = visitPreOrderImpl(subValue, inputType, containingElements, visitor); + if (copiedList != null) { + if (newValue != ABSENCE_SENTINEL) { + copiedList.add(newValue); + } + } else if (hasChanged(newValue, subValue)) { + // go into copy mode because something has changed + // copy previous values up to this point + copiedList = ImmutableList.builder(); + for (int j = 0; j < i; j++) { + copiedList.add(newList.get(j)); + } + if (newValue != ABSENCE_SENTINEL) { + copiedList.add(newValue); + } + } + i++; + } + if (copiedList != null) { + return copiedList.build(); + } else { + return newList; + } + } else { + return null; + } + } + + private static boolean hasChanged(Object newValue, Object oldValue) { + return newValue != oldValue || newValue == ABSENCE_SENTINEL; + } + + private static void setNewValue(Map newMap, String key, Object newValue) { + if (newValue == ABSENCE_SENTINEL) { + newMap.remove(key); + } else { + newMap.put(key, newValue); + } + } + +} diff --git a/src/main/java/graphql/analysis/values/ValueVisitor.java b/src/main/java/graphql/analysis/values/ValueVisitor.java new file mode 100644 index 0000000000..2d6caa545e --- /dev/null +++ b/src/main/java/graphql/analysis/values/ValueVisitor.java @@ -0,0 +1,125 @@ +package graphql.analysis.values; + +import graphql.PublicSpi; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputSchemaElement; +import graphql.schema.GraphQLInputValueDefinition; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLScalarType; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * A visitor callback used by {@link ValueTraverser} + */ +@PublicSpi +public interface ValueVisitor { + + /** + * This magic sentinel value indicates that a value should be removed from a list or object versus being set to null, + * that is the difference between a value not being present and a value being null + */ + Object ABSENCE_SENTINEL = new Object() { + @Override + public String toString() { + return "ABSENCE_SENTINEL"; + } + }; + + /** + * Represents the elements that leads to a value and type + */ + interface InputElements { + + /** + * @return then list of input schema elements that lead to an input value. + */ + List getInputElements(); + + /** + * This is the list of input schema elements that are unwrapped, e.g. + * {@link GraphQLList} and {@link graphql.schema.GraphQLNonNull} types have been removed + * + * @return then list of {@link GraphQLInputValueDefinition} elements that lead to an input value. + */ + List getUnwrappedInputElements(); + + /** + * This is the last {@link GraphQLInputValueDefinition} that pointed to the value during a callback. This will + * be either a {@link graphql.schema.GraphQLArgument} or a {@link GraphQLInputObjectField} + * + * @return the last {@link GraphQLInputValueDefinition} that contains this value + */ + GraphQLInputValueDefinition getLastInputValueDefinition(); + } + + /** + * This is called when a scalar value is encountered + * + * @param coercedValue the value that is in coerced form + * @param inputType the type of scalar + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Object visitScalarValue(@Nullable Object coercedValue, GraphQLScalarType inputType, InputElements inputElements) { + return coercedValue; + } + + /** + * This is called when an enum value is encountered + * + * @param coercedValue the value that is in coerced form + * @param inputType the type of enum + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Object visitEnumValue(@Nullable Object coercedValue, GraphQLEnumType inputType, InputElements inputElements) { + return coercedValue; + } + + /** + * This is called when an input object field value is encountered + * + * @param coercedValue the value that is in coerced form + * @param inputObjectType the input object type containing the input field + * @param inputObjectField the input object field + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Object visitInputObjectFieldValue(@Nullable Object coercedValue, GraphQLInputObjectType inputObjectType, GraphQLInputObjectField inputObjectField, InputElements inputElements) { + return coercedValue; + } + + /** + * This is called when an input object value is encountered. + * + * @param coercedValue the value that is in coerced form + * @param inputObjectType the input object type + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Map visitInputObjectValue(@Nullable Map coercedValue, GraphQLInputObjectType inputObjectType, InputElements inputElements) { + return coercedValue; + } + + /** + * This is called when an input list value is encountered. + * + * @param coercedValue the value that is in coerced form + * @param listInputType the input list type + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable List visitListValue(@Nullable List coercedValue, GraphQLList listInputType, InputElements inputElements) { + return coercedValue; + } +} diff --git a/src/main/java/graphql/schema/GraphQLInputSchemaElement.java b/src/main/java/graphql/schema/GraphQLInputSchemaElement.java new file mode 100644 index 0000000000..86b5b620f1 --- /dev/null +++ b/src/main/java/graphql/schema/GraphQLInputSchemaElement.java @@ -0,0 +1,10 @@ +package graphql.schema; + +import graphql.PublicApi; + +/** + * A schema element that is concerned with input. + */ +@PublicApi +public interface GraphQLInputSchemaElement extends GraphQLSchemaElement { +} diff --git a/src/main/java/graphql/schema/GraphQLInputType.java b/src/main/java/graphql/schema/GraphQLInputType.java index 4999cbe4fe..46fcf91301 100644 --- a/src/main/java/graphql/schema/GraphQLInputType.java +++ b/src/main/java/graphql/schema/GraphQLInputType.java @@ -8,5 +8,5 @@ * to {@link graphql.schema.GraphQLOutputType}s which can only be used as graphql response output. */ @PublicApi -public interface GraphQLInputType extends GraphQLType { +public interface GraphQLInputType extends GraphQLType, GraphQLInputSchemaElement { } diff --git a/src/main/java/graphql/schema/GraphQLInputValueDefinition.java b/src/main/java/graphql/schema/GraphQLInputValueDefinition.java index b0f3af62d4..bce4e31fa5 100644 --- a/src/main/java/graphql/schema/GraphQLInputValueDefinition.java +++ b/src/main/java/graphql/schema/GraphQLInputValueDefinition.java @@ -11,7 +11,7 @@ * @see graphql.schema.GraphQLArgument */ @PublicApi -public interface GraphQLInputValueDefinition extends GraphQLDirectiveContainer { +public interface GraphQLInputValueDefinition extends GraphQLDirectiveContainer, GraphQLInputSchemaElement { T getType(); } diff --git a/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy b/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy new file mode 100644 index 0000000000..9a29cdb827 --- /dev/null +++ b/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy @@ -0,0 +1,793 @@ +package graphql.analysis.values + +import graphql.ExecutionInput +import graphql.GraphQL +import graphql.TestUtil +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import graphql.schema.DataFetchingEnvironmentImpl +import graphql.schema.GraphQLEnumType +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLFieldsContainer +import graphql.schema.GraphQLInputObjectField +import graphql.schema.GraphQLInputObjectType +import graphql.schema.GraphQLInputSchemaElement +import graphql.schema.GraphQLList +import graphql.schema.GraphQLNamedSchemaElement +import graphql.schema.GraphQLScalarType +import graphql.schema.idl.SchemaDirectiveWiring +import graphql.schema.idl.SchemaDirectiveWiringEnvironment +import spock.lang.Specification + +import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring +import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring + +class ValueTraverserTest extends Specification { + + def sdl = """ + type Query { + field(arg1 : ComplexInput, arg2 : ComplexInput, stringArg : String, enumArg : RGB) : String + } + + input ComplexInput { + complexListField : [[ComplexInput!]] + objectField : InnerInput + listField : [Int!] + stringField : String + enumField : RGB + } + + input InnerInput { + innerListField : [Int!] + innerStringField : String + innerEnumField : RGB + } + + + enum RGB { + RED,GREEN,BLUE + } + """ + + def schema = TestUtil.schema(sdl) + + class CountingVisitor implements ValueVisitor { + + Map visits = [:] + + private int bumpCount(String name) { + visits.compute(name, { k, v -> return v == null ? 0 : ++v }) + } + + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, InputElements inputElements) { + bumpCount("scalar") + return coercedValue + } + + @Override + Object visitEnumValue(Object coercedValue, GraphQLEnumType inputType, InputElements inputElements) { + bumpCount("enum") + return coercedValue + } + + @Override + Object visitInputObjectFieldValue(Object coercedValue, GraphQLInputObjectType inputObjectType, GraphQLInputObjectField inputObjectField, InputElements inputElements) { + bumpCount("objectField") + return coercedValue + } + + @Override + Map visitInputObjectValue(Map coercedValue, GraphQLInputObjectType inputObjectType, InputElements inputElements) { + bumpCount("object") + return coercedValue + } + + @Override + List visitListValue(List coercedValue, GraphQLList listInputType, InputElements inputElements) { + bumpCount("list") + return coercedValue + } + } + + def "can traverse and changes nothing at all"() { + + def fieldDef = this.schema.getObjectType("Query").getFieldDefinition("field") + + def innerInput = [ + innerListField : [6, 7, 8], + innerStringField: "Inner", + innerEnumField : "RED", + ] + def complexInput = [ + complexListField: [[[objectField: innerInput, complexListField: [[]], stringField: "There", enumField: "GREEN"]]], + objectField : [innerStringField: "World", innerEnumField: "BLUE"], + listField : [1, 2, 3, 4, 5], + stringField : "Hello", + enumField : "RED", + ] + Map originalValues = [ + arg1 : complexInput, + arg2 : null, + stringArg : "Hello", + enumArg : "RGB", + noFieldData: "wat", + ] + def visitor = new CountingVisitor() + when: + def newValues = ValueTraverser.visitPreOrder(originalValues, fieldDef, visitor) + then: + // nothing changed - its the same object + newValues === originalValues + visitor.visits == ["scalar": 12, "enum": 4, "list": 5, "object": 4, "objectField": 13] + + + when: + def originalDFE = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().fieldDefinition(fieldDef).graphQLSchema(this.schema).arguments(originalValues).build() + def newDFE = ValueTraverser.visitPreOrder(originalDFE, visitor) + + then: + newDFE === originalDFE + + when: + def graphQLArgument = fieldDef.getArgument("arg1") + def newValue = ValueTraverser.visitPreOrder(complexInput, graphQLArgument, visitor) + + then: + complexInput === newValue + } + + def "can change simple values"() { + def fieldDef = this.schema.getObjectType("Query").getFieldDefinition("field") + + def innerInput = [ + innerListField : [6, 7, 8], + innerStringField: "Inner", + innerEnumField : "RED", + ] + def complexInput = [ + complexListField: [[[objectField: innerInput, complexListField: [[]], stringField: "There", enumField: "GREEN"]]], + objectField : [innerStringField: "World", innerEnumField: "BLUE"], + listField : [1, 2, 3, 4, 5], + stringField : "Hello", + enumField : "RED", + ] + Map originalValues = [ + arg1 : complexInput, + arg2 : null, + stringArg : "Hello", + enumArg : "BLUE", + noFieldData: "wat", + ] + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + if (coercedValue instanceof String) { + def val = coercedValue as String + return val.toUpperCase().reverse() + } + if (coercedValue instanceof Number) { + return coercedValue * 1000 + } + return coercedValue; + } + + @Override + Object visitEnumValue(Object coercedValue, GraphQLEnumType inputType, ValueVisitor.InputElements inputElements) { + def val = coercedValue as String + return val.toLowerCase().reverse() + } + } + when: + def newValues = ValueTraverser.visitPreOrder(originalValues, fieldDef, visitor) + then: + // numbers are 1000 greater and strings are upper case reversed and enums are lower cased reversed + newValues == [ + arg1 : [complexListField: [[[ + objectField : [ + innerListField : [6000, 7000, 8000], + innerStringField: "RENNI", + innerEnumField : "der" + ], + complexListField: [[]], + stringField : "EREHT", + enumField : "neerg"]]], + objectField : [innerStringField: "DLROW", innerEnumField: "eulb"], + listField : [1000, 2000, 3000, 4000, 5000], + stringField : "OLLEH", + enumField : "der"], + arg2 : null, + stringArg : "OLLEH", + enumArg : "eulb", + noFieldData: "wat" + ] + } + + def "can change a list midway through "() { + def sdl = """ + type Query { + field(arg : [Int]!) : String + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: [1, 2, 3, 4]] + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + if (coercedValue == 3) { + return 33 + } + return coercedValue + } + } + when: + def newValues = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + then: + newValues == [arg: [1, 2, 33, 4]] + } + + def "can change an object midway through "() { + def sdl = """ + type Query { + field(arg : Input!) : String + } + + input Input { + name : String + age : Int + input : Input + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [name: "Tess", age: 42, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 42]] + ] + ] + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + if (coercedValue == 42) { + return 24 + } + return coercedValue + } + } + when: + def actual = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + + def expected = [arg: + [name: "Tess", age: 24, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 24] + ] + ] + ] + then: + actual == expected + + + // can change a DFE arguments + when: + def startingDFE = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().fieldDefinition(fieldDef).arguments(argValues).build() + def newDFE = ValueTraverser.visitPreOrder(startingDFE, visitor) + + then: + newDFE.getArguments() == expected + newDFE.getFieldDefinition() == fieldDef + + // can change a single arguments + when: + def newValues = ValueTraverser.visitPreOrder(argValues['arg'], fieldDef.getArgument("arg"), visitor) + + then: + newValues == [name: "Tess", age: 24, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 24] + ] + ] + } + + def "can handle a null changes"() { + def sdl = """ + type Query { + field(arg : Input!) : String + } + + input Input { + listField : [String!] + objectField : Input + stringField : String + leaveAloneField : String + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [ + listField : ["a", "b", "c"], + objectField : [listField: ["a", "b", "c"]], + stringField : "s", + leaveAloneField: "ok" + ] + ] + def visitor = new ValueVisitor() { + + @Override + Object visitInputObjectFieldValue(Object coercedValue, GraphQLInputObjectType inputObjectType, GraphQLInputObjectField inputObjectField, ValueVisitor.InputElements inputElements) { + if (inputObjectField.name == "leaveAloneField") { + return coercedValue + } + return null + } + + } + when: + def actual = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + + def expected = [arg: + [ + listField : null, + objectField : null, + stringField : null, + leaveAloneField: "ok", + ] + ] + then: + actual == expected + } + + def "can turn nulls into actual values"() { + def sdl = """ + type Query { + field(arg : Input) : String + } + + input Input { + listField : [String] + objectField : Input + stringField : String + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [ + listField : null, + objectField: null, + stringField: null, + ] + ] + def visitor = new ValueVisitor() { + + @Override + Object visitInputObjectFieldValue(Object coercedValue, GraphQLInputObjectType inputObjectType, GraphQLInputObjectField inputObjectField, ValueVisitor.InputElements inputElements) { + if (inputObjectField.name == "listField") { + return ["a", "b", "c"] + } + if (inputObjectField.name == "objectField") { + return [listField: ["x", "y", "z"], stringField: ["will be overwritten"]] + } + if (inputObjectField.name == "stringField") { + return "stringValue" + } + return coercedValue + } + + } + when: + def actual = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + + def expected = [arg: + [ + listField : ["a", "b", "c"], + objectField: [listField: ["a", "b", "c"], stringField: "stringValue"], + stringField: "stringValue", + ] + ] + then: + actual == expected + } + + + def "can use the sentinel to remove elements"() { + def sdl = """ + + type Query { + field(arg : Input!, arg2 : String) : String + } + + input Input { + name : String + age : Int + extraInput : ExtraInput + listInput : [Int] + } + + input ExtraInput { + name : String + gone : Boolean + age : Int + otherInput : ExtraInput + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg : + [name : "Tess", + age : 42, + extraInput: + [name : "Tom", + age : 33, + gone : true, + otherInput: [ + name: "Ted", + age : 42] + ], + listInput : [1, 2, 3, 4, 5, 6, 7, 8] + ], + arg2: "Gone-ski" + ] + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + def fieldName = inputElements.getLastInputValueDefinition().name + if (fieldName == "age") { + return ABSENCE_SENTINEL + } + if (coercedValue == "Gone-ski") { + return ABSENCE_SENTINEL + } + if (coercedValue == 4 || coercedValue == 7) { + return ABSENCE_SENTINEL + } + return coercedValue + } + + @Override + Object visitInputObjectFieldValue(Object coercedValue, GraphQLInputObjectType inputObjectType, GraphQLInputObjectField inputObjectField, ValueVisitor.InputElements inputElements) { + def fieldName = inputElements.getLastInputValueDefinition().name + if (fieldName == "otherInput") { + return ABSENCE_SENTINEL + } + if (fieldName == "gone") { + return ABSENCE_SENTINEL + } + return coercedValue + } + } + when: + def newValues = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + then: + newValues == [arg: + [name : "Tess", + extraInput: + [name: "Tom"], + listInput : [1, 2, 3, 5, 6, 8] + ] + ] + } + + def "can get give access to all elements and unwrapped elements"() { + def sdl = """ + + type Query { + field(arg : Input! ) : String + } + + input Input { + name : String + age : Int + objectField : [[Input!]]! + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [name : "Tess", + age : 42, + objectField: [[ + [ + name : "Tom", + age : 33, + objectField: [[ + [ + name: "Ted", + age : 42 + ] + ]] + ] + ]] + ] + ] + def captureAll = [] + def captureUnwrapped = [] + def last = "" + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + if (coercedValue == "Ted") { + captureAll = inputElements.inputElements.collect { testStr(it) } + captureUnwrapped = inputElements.unwrappedInputElements.collect { testStr(it) } + last = inputElements.lastInputValueDefinition.name + } + return coercedValue + } + + String testStr(GraphQLInputSchemaElement inputSchemaElement) { + if (inputSchemaElement instanceof GraphQLNamedSchemaElement) { + return inputSchemaElement.name + } + return inputSchemaElement.toString() + } + } + when: + def newValues = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + then: + newValues == argValues + last == "name" + captureAll == [ + "arg", + "Input!", + "Input", + "objectField", + "[[Input!]]!", + "[[Input!]]", + "[Input!]", + "Input!", + "Input", + "objectField", + "[[Input!]]!", + "[[Input!]]", + "[Input!]", + "Input!", + "Input", + "name", + "String", + ] + captureUnwrapped == [ + "arg", + "Input", + "objectField", + "Input", + "objectField", + "Input", + "name", + "String", + ] + } + + def "can get access to directives"() { + def sdl = """ + directive @d(name : String!) on ARGUMENT_DEFINITION | INPUT_OBJECT | INPUT_FIELD_DEFINITION + + type Query { + field(arg : Input! @d(name : "argDirective") ) : String + } + + input Input { + name : String @d(name : "nameDirective") + age : Int @d(name : "ageDirective") + input : Input @d(name : "inputDirective") + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [name: "Tess", age: 42, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 42]] + ] + ] + def capture = [] + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + checkDirectives(inputElements) + return coercedValue + } + + + private void checkDirectives(ValueVisitor.InputElements inputElements) { + def lastElement = inputElements.getLastInputValueDefinition() + def directive = lastElement.getAppliedDirective("d") + if (directive != null) { + def elementNames = inputElements.getInputElements().collect( + { it -> + if (it instanceof GraphQLNamedSchemaElement) { + return it.name + } else { + it.toString() + } + }) + .join(":") + def value = directive.getArgument("name").value + capture.add(elementNames + "@" + value) + } + } + } + when: + def newValues = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + then: + newValues == argValues + capture == [ + "arg:Input!:Input:name:String@nameDirective", + "arg:Input!:Input:age:Int@ageDirective", + + "arg:Input!:Input:input:Input:name:String@nameDirective", + "arg:Input!:Input:input:Input:age:Int@ageDirective", + + "arg:Input!:Input:input:Input:input:Input:name:String@nameDirective", + "arg:Input!:Input:input:Input:input:Input:age:Int@ageDirective" + ] + } + + def "can follow directives and change input"() { + def sdl = """ + directive @stripHtml on ARGUMENT_DEFINITION | INPUT_OBJECT | INPUT_FIELD_DEFINITION + + type Query { + field(arg : Input!) : String + } + + input Input { + name : String @stripHtml + age : Int + input : Input + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [arg: + [name: "Tess", age: 42, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 42]] + ] + ] + + def visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + def lastElement = inputElements.getLastInputValueDefinition() + def directive = lastElement.getAppliedDirective("stripHtml") + if (directive != null) { + def v = String.valueOf(coercedValue) + return v.replaceAll(//, '').replaceAll(/<.*?>/, '') + } + return coercedValue + } + + } + when: + def newValues = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + then: + newValues == [arg: + [name: "Tess", age: 42, input: + [name: "Tom", age: 33, input: + [name: "Ted", age: 42]] + ] + ] + } + + def "an integration test showing how to change values"() { + def sdl = """ +directive @stripHtml on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + +type Query { + searchProfile(contains: String! @stripHtml, limit: Int): Profile! +} + +type Mutation { + signUp(input: SignUpInput!): Profile! +} + +input SignUpInput { + username: String! @stripHtml + password: String! + firstName: String! + lastName: String! +} + +type Profile { + username: String! + fullName: String! +} +""" + def schemaDirectiveWiring = new SchemaDirectiveWiring() { + @Override + GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment env) { + GraphQLFieldsContainer fieldsContainer = env.getFieldsContainer() + GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition() + + final DataFetcher originalDF = env.getCodeRegistry().getDataFetcher(fieldsContainer, fieldDefinition) + final DataFetcher newDF = { DataFetchingEnvironment originalEnv -> + ValueVisitor visitor = new ValueVisitor() { + @Override + Object visitScalarValue(Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + def container = inputElements.getLastInputValueDefinition() + if (container.hasAppliedDirective("stripHtml")) { + return stripHtml(coercedValue) + } + return coercedValue + } + + private String stripHtml(coercedValue) { + return String.valueOf(coercedValue) + .replaceAll(//, '') + .replaceAll(/<.*?>/, '') + } + } + DataFetchingEnvironment newEnv = ValueTraverser.visitPreOrder(originalEnv, visitor) + return originalDF.get(newEnv); + } + + env.getCodeRegistry().dataFetcher(fieldsContainer, fieldDefinition, newDF) + + return fieldDefinition + } + } + + DataFetcher searchProfileDF = { env -> + def containsArg = env.getArgument("contains") as String + return [username: containsArg] + + } + DataFetcher signUpDF = { DataFetchingEnvironment env -> + def inputArg = env.getArgument("input") as Map + def inputUserName = inputArg["username"] + return [username: inputUserName] + } + def runtimeWiring = newRuntimeWiring().directiveWiring(schemaDirectiveWiring) + .type(newTypeWiring("Query").dataFetcher("searchProfile", searchProfileDF)) + .type(newTypeWiring("Mutation").dataFetcher("signUp", signUpDF)) + .build() + def schema = TestUtil.schema(sdl, runtimeWiring) + def graphQL = GraphQL.newGraphQL(schema).build() + + def query = """ + query q { + searchProfile(contains : "someHtml") { + username + } + } + + mutation m { + signUp(input : { + username: "bbakerman" + password: "hunter2" + firstName: "Brad" + lastName: "Baker" + } + ) { + username + } + } +""" + + when: + def executionInput = ExecutionInput.newExecutionInput(query).operationName("q").build() + def er = graphQL.execute(executionInput) + then: + er.errors.isEmpty() + er.data == [searchProfile: [username: "someHtml"]] + + // mutation + when: + executionInput = ExecutionInput.newExecutionInput(query).operationName("m").build() + er = graphQL.execute(executionInput) + then: + er.errors.isEmpty() + er.data == [signUp: [username: "bbakerman"]] + } +} From 03e10fbb6d079c353d996aab1b5f9b19697d0918 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 10:12:23 +1100 Subject: [PATCH 288/294] Align Boolean parseValue with JS implementation --- .../scalar/GraphqlBooleanCoercing.java | 7 ++-- src/main/resources/i18n/Scalars.properties | 1 + src/main/resources/i18n/Scalars_de.properties | 1 + .../groovy/graphql/ScalarsBooleanTest.groovy | 39 +++++++++++++++++-- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java b/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java index 266e64c4a5..6ef64c5976 100644 --- a/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlBooleanCoercing.java @@ -68,13 +68,12 @@ private Boolean serializeImpl(@NotNull Object input, @NotNull Locale locale) { @NotNull private Boolean parseValueImpl(@NotNull Object input, @NotNull Locale locale) { - Boolean result = convertImpl(input); - if (result == null) { + if (!(input instanceof Boolean)) { throw new CoercingParseValueException( - i18nMsg(locale, "Boolean.notBoolean", typeName(input)) + i18nMsg(locale, "Boolean.unexpectedRawValueType", typeName(input)) ); } - return result; + return (Boolean) input; } private static boolean parseLiteralImpl(@NotNull Object input, @NotNull Locale locale) { diff --git a/src/main/resources/i18n/Scalars.properties b/src/main/resources/i18n/Scalars.properties index 4dbc68d683..89fc5314f0 100644 --- a/src/main/resources/i18n/Scalars.properties +++ b/src/main/resources/i18n/Scalars.properties @@ -27,5 +27,6 @@ Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' b # Boolean.notBoolean=Expected a value that can be converted to type ''Boolean'' but it was a ''{0}'' Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}'' +Boolean.unexpectedRawValueType=Expected a Boolean input, but it was a ''{0}'' # String.unexpectedRawValueType=Expected a String input, but it was a ''{0}'' diff --git a/src/main/resources/i18n/Scalars_de.properties b/src/main/resources/i18n/Scalars_de.properties index ce045d8404..4c2761d1cf 100644 --- a/src/main/resources/i18n/Scalars_de.properties +++ b/src/main/resources/i18n/Scalars_de.properties @@ -30,5 +30,6 @@ Float.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''Floa # Boolean.notBoolean=Erwartet wurde ein Wert, der in den Typ ''Boolean'' konvertiert werden kann, aber es war ein ''{0}'' Boolean.unexpectedAstType=Erwartet wurde ein AST type ''BooleanValue'', aber es war ein ''{0}'' +Boolean.unexpectedRawValueType=Erwartet wurde eine Boolean-Eingabe, aber es war ein ''{0}'' # String.unexpectedRawValueType=Erwartet wurde eine String-Eingabe, aber es war ein ''{0}'' diff --git a/src/test/groovy/graphql/ScalarsBooleanTest.groovy b/src/test/groovy/graphql/ScalarsBooleanTest.groovy index c059d5c53c..5b316765ad 100644 --- a/src/test/groovy/graphql/ScalarsBooleanTest.groovy +++ b/src/test/groovy/graphql/ScalarsBooleanTest.groovy @@ -53,7 +53,6 @@ class ScalarsBooleanTest extends Specification { def "Boolean serialize #value into #result (#result.class)"() { expect: Scalars.GraphQLBoolean.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result - Scalars.GraphQLBoolean.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result where: value | result @@ -75,7 +74,6 @@ class ScalarsBooleanTest extends Specification { def "Boolean serialize #value into #result (#result.class) with deprecated methods"() { expect: Scalars.GraphQLBoolean.getCoercing().serialize(value) == result // Retain deprecated method for test coverage - Scalars.GraphQLBoolean.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage where: value | result @@ -111,6 +109,28 @@ class ScalarsBooleanTest extends Specification { "f" | _ } + @Unroll + def "Boolean parseValue #value into #result (#result.class)"() { + expect: + Scalars.GraphQLBoolean.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + true | true + false | false + } + + @Unroll + def "Boolean parseValue #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLBoolean.getCoercing().parseValue(value) == result // Retain deprecated method for test coverage + + where: + value | result + true | true + false | false + } + @Unroll def "parseValue throws exception for invalid input #value"() { when: @@ -119,8 +139,19 @@ class ScalarsBooleanTest extends Specification { thrown(CoercingParseValueException) where: - value | _ - new Object() | _ + value | _ + new Object() | _ + "false" | _ + "true" | _ + "True" | _ + 0 | _ + 1 | _ + -1 | _ + new Long(42345784398534785l) | _ + new Double(42.3) | _ + new Float(42.3) | _ + Integer.MAX_VALUE + 1l | _ + Integer.MIN_VALUE - 1l | _ } } From e042b8e8a60753ad77414082554b70e74b152946 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 10:36:55 +1100 Subject: [PATCH 289/294] Align Float parseValue with JS implementation --- .../graphql/scalar/GraphqlFloatCoercing.java | 9 +++- src/main/resources/i18n/Scalars.properties | 1 + src/main/resources/i18n/Scalars_de.properties | 1 + .../groovy/graphql/ScalarsFloatTest.groovy | 50 ++++++++++++++++++- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java index cb7d43b43b..683f915634 100644 --- a/src/main/java/graphql/scalar/GraphqlFloatCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlFloatCoercing.java @@ -64,13 +64,20 @@ private Double serialiseImpl(Object input, @NotNull Locale locale) { } @NotNull - private Double parseValueImpl(Object input, @NotNull Locale locale) { + private Double parseValueImpl(@NotNull Object input, @NotNull Locale locale) { + if (!(input instanceof Number)) { + throw new CoercingParseValueException( + i18nMsg(locale, "Float.unexpectedRawValueType", typeName(input)) + ); + } + Double result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( i18nMsg(locale, "Float.notFloat", typeName(input)) ); } + return result; } diff --git a/src/main/resources/i18n/Scalars.properties b/src/main/resources/i18n/Scalars.properties index 89fc5314f0..39fc6b4105 100644 --- a/src/main/resources/i18n/Scalars.properties +++ b/src/main/resources/i18n/Scalars.properties @@ -24,6 +24,7 @@ ID.unexpectedAstType=Expected an AST type of ''IntValue'' or ''StringValue'' but # Float.notFloat=Expected a value that can be converted to type ''Float'' but it was a ''{0}'' Float.unexpectedAstType=Expected an AST type of ''IntValue'' or ''FloatValue'' but it was a ''{0}'' +Float.unexpectedRawValueType=Expected a Number input, but it was a ''{0}'' # Boolean.notBoolean=Expected a value that can be converted to type ''Boolean'' but it was a ''{0}'' Boolean.unexpectedAstType=Expected an AST type of ''BooleanValue'' but it was a ''{0}'' diff --git a/src/main/resources/i18n/Scalars_de.properties b/src/main/resources/i18n/Scalars_de.properties index 4c2761d1cf..02b1b27a75 100644 --- a/src/main/resources/i18n/Scalars_de.properties +++ b/src/main/resources/i18n/Scalars_de.properties @@ -27,6 +27,7 @@ ID.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''StringV # Float.notFloat=Erwartet wurde ein Wert, der in den Typ ''Float'' konvertiert werden kann, aber es war ein ''{0}'' Float.unexpectedAstType=Erwartet wurde ein AST type von ''IntValue'' oder ''FloatValue'', aber es war ein ''{0}'' +Float.unexpectedRawValueType=Erwartet wurde eine Number-Eingabe, aber es war ein ''{0}'' # Boolean.notBoolean=Erwartet wurde ein Wert, der in den Typ ''Boolean'' konvertiert werden kann, aber es war ein ''{0}'' Boolean.unexpectedAstType=Erwartet wurde ein AST type ''BooleanValue'', aber es war ein ''{0}'' diff --git a/src/test/groovy/graphql/ScalarsFloatTest.groovy b/src/test/groovy/graphql/ScalarsFloatTest.groovy index 07fea26fd1..6f6a195d65 100644 --- a/src/test/groovy/graphql/ScalarsFloatTest.groovy +++ b/src/test/groovy/graphql/ScalarsFloatTest.groovy @@ -58,7 +58,6 @@ class ScalarsFloatTest extends Specification { def "Float serialize #value into #result (#result.class)"() { expect: Scalars.GraphQLFloat.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result - Scalars.GraphQLFloat.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result where: value | result @@ -84,7 +83,6 @@ class ScalarsFloatTest extends Specification { def "Float serialize #value into #result (#result.class) with deprecated methods"() { expect: Scalars.GraphQLFloat.getCoercing().serialize(value) == result // Retain deprecated method for coverage - Scalars.GraphQLFloat.getCoercing().parseValue(value) == result // Retain deprecated method for coverage where: value | result @@ -131,6 +129,51 @@ class ScalarsFloatTest extends Specification { Float.NEGATIVE_INFINITY.toString() | _ } + @Unroll + def "Float parseValue #value into #result (#result.class)"() { + expect: + Scalars.GraphQLFloat.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + 42.0000d | 42 + new Integer(42) | 42 + new BigInteger("42") | 42 + new BigDecimal("42") | 42 + new BigDecimal("4.2") | 4.2d + 42.3f | 42.3d + 42.0d | 42d + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567d + new AtomicInteger(42) | 42 + Double.MAX_VALUE | Double.MAX_VALUE + Double.MIN_VALUE | Double.MIN_VALUE + } + + @Unroll + def "Float parseValue #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLFloat.getCoercing().parseValue(value) == result // Retain deprecated method for coverage + + where: + value | result + 42.0000d | 42 + new Integer(42) | 42 + new BigInteger("42") | 42 + new BigDecimal("42") | 42 + new BigDecimal("4.2") | 4.2d + 42.3f | 42.3d + 42.0d | 42d + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567d + new AtomicInteger(42) | 42 + Double.MAX_VALUE | Double.MAX_VALUE + Double.MIN_VALUE | Double.MIN_VALUE + } + + @Unroll def "parseValue throws exception for invalid input #value"() { when: @@ -154,6 +197,9 @@ class ScalarsFloatTest extends Specification { Float.POSITIVE_INFINITY.toString() | _ Float.NEGATIVE_INFINITY | _ Float.NEGATIVE_INFINITY.toString() | _ + "42" | _ + "42.123" | _ + "-1" | _ } } From 2c514ca49c77068fac789d2c667ed096da936f29 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 15:01:13 +1100 Subject: [PATCH 290/294] Align integer parseValue coercion with JS implementation --- .../graphql/scalar/GraphqlIntCoercing.java | 8 +++- src/test/groovy/graphql/ScalarsIntTest.groovy | 47 ++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlIntCoercing.java b/src/main/java/graphql/scalar/GraphqlIntCoercing.java index 1ca0f5b2ee..f5dbbc0490 100644 --- a/src/main/java/graphql/scalar/GraphqlIntCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlIntCoercing.java @@ -63,7 +63,13 @@ private Integer serialiseImpl(Object input, @NotNull Locale locale) { } @NotNull - private Integer parseValueImpl(Object input, @NotNull Locale locale) { + private Integer parseValueImpl(@NotNull Object input, @NotNull Locale locale) { + if (!(input instanceof Number)) { + throw new CoercingParseValueException( + i18nMsg(locale, "Int.notInt", typeName(input)) + ); + } + Integer result = convertImpl(input); if (result == null) { throw new CoercingParseValueException( diff --git a/src/test/groovy/graphql/ScalarsIntTest.groovy b/src/test/groovy/graphql/ScalarsIntTest.groovy index 883d9dbf17..7a5b43ed9e 100644 --- a/src/test/groovy/graphql/ScalarsIntTest.groovy +++ b/src/test/groovy/graphql/ScalarsIntTest.groovy @@ -60,7 +60,6 @@ class ScalarsIntTest extends Specification { def "Int serialize #value into #result (#result.class)"() { expect: Scalars.GraphQLInt.getCoercing().serialize(value, GraphQLContext.default, Locale.default) == result - Scalars.GraphQLInt.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result where: value | result @@ -85,7 +84,6 @@ class ScalarsIntTest extends Specification { def "Int serialize #value into #result (#result.class) with deprecated methods"() { expect: Scalars.GraphQLInt.getCoercing().serialize(value) == result // Retain deprecated for test coverage - Scalars.GraphQLInt.getCoercing().parseValue(value) == result // Retain deprecated for test coverage where: value | result @@ -126,6 +124,48 @@ class ScalarsIntTest extends Specification { new Object() | _ } + @Unroll + def "Int parseValue #value into #result (#result.class)"() { + expect: + Scalars.GraphQLInt.getCoercing().parseValue(value, GraphQLContext.default, Locale.default) == result + + where: + value | result + new Integer(42) | 42 + new BigInteger("42") | 42 + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567 + new AtomicInteger(42) | 42 + Integer.MAX_VALUE | Integer.MAX_VALUE + Integer.MIN_VALUE | Integer.MIN_VALUE + 42.0000d | 42 + new BigDecimal("42") | 42 + 42.0f | 42 + 42.0d | 42 + } + + @Unroll + def "Int parseValue #value into #result (#result.class) with deprecated methods"() { + expect: + Scalars.GraphQLInt.getCoercing().parseValue(value) == result // Retain deprecated for test coverage + + where: + value | result + 42.0000d | 42 + new Integer(42) | 42 + new BigInteger("42") | 42 + new BigDecimal("42") | 42 + 42.0f | 42 + 42.0d | 42 + new Byte("42") | 42 + new Short("42") | 42 + 1234567l | 1234567 + new AtomicInteger(42) | 42 + Integer.MAX_VALUE | Integer.MAX_VALUE + Integer.MIN_VALUE | Integer.MIN_VALUE + } + @Unroll def "parseValue throws exception for invalid input #value"() { when: @@ -144,6 +184,9 @@ class ScalarsIntTest extends Specification { Integer.MAX_VALUE + 1l | _ Integer.MIN_VALUE - 1l | _ new Object() | _ + "42" | _ + "42.0000" | _ + "-1" | _ } } From 64934d604f308ce52691c34d4190b45a1038d3b8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:13:10 +1100 Subject: [PATCH 291/294] Add separate exception message for values outside integer range --- .../graphql/scalar/GraphqlIntCoercing.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlIntCoercing.java b/src/main/java/graphql/scalar/GraphqlIntCoercing.java index f5dbbc0490..84577047ee 100644 --- a/src/main/java/graphql/scalar/GraphqlIntCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlIntCoercing.java @@ -64,19 +64,45 @@ private Integer serialiseImpl(Object input, @NotNull Locale locale) { @NotNull private Integer parseValueImpl(@NotNull Object input, @NotNull Locale locale) { + if (input instanceof Integer) { + return (Integer) input; + } + if (!(input instanceof Number)) { throw new CoercingParseValueException( i18nMsg(locale, "Int.notInt", typeName(input)) ); } - Integer result = convertImpl(input); + BigInteger result = convertParseValueImpl(input); if (result == null) { throw new CoercingParseValueException( i18nMsg(locale, "Int.notInt", typeName(input)) ); } - return result; + + if (result.compareTo(INT_MIN) < 0 || result.compareTo(INT_MAX) > 0) { + throw new CoercingParseValueException( + i18nMsg(locale, "Int.outsideRange", result.toString()) + ); + } + return result.intValueExact(); + } + + private BigInteger convertParseValueImpl(Object input) { + BigDecimal value; + try { + value = new BigDecimal(input.toString()); + } catch (NumberFormatException e) { + return null; + } + + try { + return value.toBigIntegerExact(); + } catch (ArithmeticException e) { + // Exception if number has non-zero fractional part + return null; + } } private static int parseLiteralImpl(Object input, @NotNull Locale locale) { From 954ec14b6b9adbd69894037893e6714a258367a0 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:22:58 +1100 Subject: [PATCH 292/294] Tidy --- src/main/java/graphql/scalar/GraphqlIntCoercing.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/scalar/GraphqlIntCoercing.java b/src/main/java/graphql/scalar/GraphqlIntCoercing.java index 84577047ee..350c4f4814 100644 --- a/src/main/java/graphql/scalar/GraphqlIntCoercing.java +++ b/src/main/java/graphql/scalar/GraphqlIntCoercing.java @@ -64,16 +64,16 @@ private Integer serialiseImpl(Object input, @NotNull Locale locale) { @NotNull private Integer parseValueImpl(@NotNull Object input, @NotNull Locale locale) { - if (input instanceof Integer) { - return (Integer) input; - } - if (!(input instanceof Number)) { throw new CoercingParseValueException( i18nMsg(locale, "Int.notInt", typeName(input)) ); } + if (input instanceof Integer) { + return (Integer) input; + } + BigInteger result = convertParseValueImpl(input); if (result == null) { throw new CoercingParseValueException( From fd23fc80d43cf3002ccc96aedc48c1cf0ba04fff Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:46:19 +1100 Subject: [PATCH 293/294] Fix up tests --- .../groovy/graphql/execution/ValuesResolverTest.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy index 203d12403e..9c2ec5d2d0 100644 --- a/src/test/groovy/graphql/execution/ValuesResolverTest.groovy +++ b/src/test/groovy/graphql/execution/ValuesResolverTest.groovy @@ -56,8 +56,8 @@ class ValuesResolverTest extends Specification { inputType | variableType | inputValue || outputValue GraphQLInt | new TypeName("Int") | 100 || 100 GraphQLString | new TypeName("String") | 'someString' || 'someString' - GraphQLBoolean | new TypeName("Boolean") | 'true' || true - GraphQLFloat | new TypeName("Float") | '42.43' || 42.43d + GraphQLBoolean | new TypeName("Boolean") | true || true + GraphQLFloat | new TypeName("Float") | 42.43d || 42.43d } def "getVariableValues: map object as variable input"() { @@ -641,7 +641,7 @@ class ValuesResolverTest extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a value that can be converted to type 'Boolean' but it was a 'String'" + executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a Boolean input, but it was a 'String'" executionResult.errors[0].locations == [new SourceLocation(2, 35)] } @@ -679,7 +679,7 @@ class ValuesResolverTest extends Specification { executionResult.data == null executionResult.errors.size() == 1 executionResult.errors[0].errorType == ErrorType.ValidationError - executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a value that can be converted to type 'Float' but it was a 'String'" + executionResult.errors[0].message == "Variable 'input' has an invalid value: Expected a Number input, but it was a 'String'" executionResult.errors[0].locations == [new SourceLocation(2, 35)] } } \ No newline at end of file From 557ad5dc35338f94fdb973521295be2b6b057625 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Tue, 6 Dec 2022 15:32:14 +1100 Subject: [PATCH 294/294] Value visitor with applied directive argument visited (#3041) * Added a ValueTraverser allowing you to visit and optionally change input argument values * Added integration test * Added PR feedback - sentinals, new types and so on * PR tweaks * PR feedback * PR feedback 2 * More PR feedback from Andi - handled null object fields * Added the ability to visit the arguments of a field and change them * Added applied directives argument support --- .../analysis/values/ValueTraverser.java | 69 ++++++-- .../graphql/analysis/values/ValueVisitor.java | 31 ++++ .../GraphQLAppliedDirectiveArgument.java | 2 +- .../analysis/values/ValueTraverserTest.groovy | 152 ++++++++++++++++++ 4 files changed, 243 insertions(+), 11 deletions(-) diff --git a/src/main/java/graphql/analysis/values/ValueTraverser.java b/src/main/java/graphql/analysis/values/ValueTraverser.java index 162926a5f3..1cf7745aaa 100644 --- a/src/main/java/graphql/analysis/values/ValueTraverser.java +++ b/src/main/java/graphql/analysis/values/ValueTraverser.java @@ -1,10 +1,11 @@ package graphql.analysis.values; import com.google.common.collect.ImmutableList; -import graphql.Assert; import graphql.PublicApi; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLAppliedDirectiveArgument; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; @@ -23,6 +24,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static graphql.Assert.assertShouldNeverHappen; +import static graphql.Assert.assertTrue; import static graphql.analysis.values.ValueVisitor.ABSENCE_SENTINEL; /** @@ -51,10 +54,10 @@ private static class InputElements implements ValueVisitor.InputElements { private final List unwrappedInputElements; private final GraphQLInputValueDefinition lastElement; - private InputElements(GraphQLInputValueDefinition startElement) { + private InputElements(GraphQLInputSchemaElement startElement) { this.inputElements = ImmutableList.of(startElement); this.unwrappedInputElements = ImmutableList.of(startElement); - this.lastElement = startElement; + this.lastElement = startElement instanceof GraphQLInputValueDefinition ? (GraphQLInputValueDefinition) startElement : null; } private InputElements(ImmutableList inputElements) { @@ -66,7 +69,7 @@ private InputElements(ImmutableList inputElements) { List inputValDefs = unwrappedInputElements.stream() .filter(it -> it instanceof GraphQLInputValueDefinition) .map(GraphQLInputValueDefinition.class::cast).collect(Collectors.toList()); - this.lastElement = inputValDefs.get(inputValDefs.size() - 1); + this.lastElement = inputValDefs.isEmpty() ? null : inputValDefs.get(inputValDefs.size() - 1); } @@ -125,7 +128,7 @@ public static Map visitPreOrder(Map coercedArgum String key = fieldArgument.getName(); Object argValue = coercedArgumentValues.get(key); InputElements inputElements = new InputElements(fieldArgument); - Object newValue = visitPreOrderImpl(argValue, fieldArgument.getType(), inputElements, visitor); + Object newValue = visitor.visitArgumentValue(argValue, fieldArgument, inputElements); if (hasChanged(newValue, argValue)) { if (!copied) { coercedArgumentValues = new LinkedHashMap<>(coercedArgumentValues); @@ -133,12 +136,25 @@ public static Map visitPreOrder(Map coercedArgum } setNewValue(coercedArgumentValues, key, newValue); } + if (newValue != ABSENCE_SENTINEL) { + newValue = visitPreOrderImpl(argValue, fieldArgument.getType(), inputElements, visitor); + if (hasChanged(newValue, argValue)) { + if (!copied) { + coercedArgumentValues = new LinkedHashMap<>(coercedArgumentValues); + copied = true; + } + setNewValue(coercedArgumentValues, key, newValue); + } + } } return coercedArgumentValues; } /** - * This will visit a single argument of a {@link GraphQLArgument} and if the visitor changes the value, it will return a new argument + * This will visit a single argument of a {@link GraphQLArgument} and if the visitor changes the value, it will return a new argument value + *

+ * Note you cannot return the ABSENCE_SENTINEL from this method as its makes no sense to be somehow make the argument disappear. Use + * {@link #visitPreOrder(Map, GraphQLFieldDefinition, ValueVisitor)} say to remove arguments in the fields map of arguments. * * @param coercedArgumentValue the starting coerced argument value * @param argument the argument definition @@ -147,7 +163,40 @@ public static Map visitPreOrder(Map coercedArgum * @return the same value if nothing changes or a new value if the visitor changes anything */ public static Object visitPreOrder(Object coercedArgumentValue, GraphQLArgument argument, ValueVisitor visitor) { - return visitPreOrderImpl(coercedArgumentValue, argument.getType(), new InputElements(argument), visitor); + InputElements inputElements = new InputElements(argument); + Object newValue = visitor.visitArgumentValue(coercedArgumentValue, argument, inputElements); + if (newValue == ABSENCE_SENTINEL) { + assertShouldNeverHappen("It makes no sense to return the ABSENCE_SENTINEL during the visitPreOrder GraphQLArgument method"); + } + newValue = visitPreOrderImpl(newValue, argument.getType(), inputElements, visitor); + if (newValue == ABSENCE_SENTINEL) { + assertShouldNeverHappen("It makes no sense to return the ABSENCE_SENTINEL during the visitPreOrder GraphQLArgument method"); + } + return newValue; + } + + /** + * This will visit a single argument of a {@link GraphQLAppliedDirective} and if the visitor changes the value, it will return a new argument value + *

+ * Note you cannot return the ABSENCE_SENTINEL from this method as its makes no sense to be somehow make the argument disappear. + * + * @param coercedArgumentValue the starting coerced argument value + * @param argument the applied argument + * @param visitor the visitor to use + * + * @return the same value if nothing changes or a new value if the visitor changes anything + */ + public static Object visitPreOrder(Object coercedArgumentValue, GraphQLAppliedDirectiveArgument argument, ValueVisitor visitor) { + InputElements inputElements = new InputElements(argument); + Object newValue = visitor.visitAppliedDirectiveArgumentValue(coercedArgumentValue, argument, inputElements); + if (newValue == ABSENCE_SENTINEL) { + assertShouldNeverHappen("It makes no sense to return the ABSENCE_SENTINEL during the visitPreOrder GraphQLAppliedDirectiveArgument method"); + } + newValue = visitPreOrderImpl(newValue, argument.getType(), inputElements, visitor); + if (newValue == ABSENCE_SENTINEL) { + assertShouldNeverHappen("It makes no sense to return the ABSENCE_SENTINEL during the visitPreOrder GraphQLAppliedDirectiveArgument method"); + } + return newValue; } private static Object visitPreOrderImpl(Object coercedValue, GraphQLInputType startingInputType, InputElements containingElements, ValueVisitor visitor) { @@ -166,13 +215,13 @@ private static Object visitPreOrderImpl(Object coercedValue, GraphQLInputType st } else if (inputType instanceof GraphQLEnumType) { return visitor.visitEnumValue(coercedValue, (GraphQLEnumType) inputType, containingElements); } else { - return Assert.assertShouldNeverHappen("ValueTraverser can only be called on full materialised schemas"); + return assertShouldNeverHappen("ValueTraverser can only be called on full materialised schemas"); } } private static Object visitObjectValue(Object coercedValue, GraphQLInputObjectType inputObjectType, InputElements containingElements, ValueVisitor visitor) { if (coercedValue != null) { - Assert.assertTrue(coercedValue instanceof Map, () -> "A input object type MUST have an Map value"); + assertTrue(coercedValue instanceof Map, () -> "A input object type MUST have an Map value"); } @SuppressWarnings("unchecked") Map map = (Map) coercedValue; @@ -217,7 +266,7 @@ private static Object visitObjectValue(Object coercedValue, GraphQLInputObjectTy private static Object visitListValue(Object coercedValue, GraphQLList listInputType, InputElements containingElements, ValueVisitor visitor) { if (coercedValue != null) { - Assert.assertTrue(coercedValue instanceof List, () -> "A list type MUST have an List value"); + assertTrue(coercedValue instanceof List, () -> "A list type MUST have an List value"); } @SuppressWarnings("unchecked") List list = (List) coercedValue; diff --git a/src/main/java/graphql/analysis/values/ValueVisitor.java b/src/main/java/graphql/analysis/values/ValueVisitor.java index 2d6caa545e..21ae97c0a1 100644 --- a/src/main/java/graphql/analysis/values/ValueVisitor.java +++ b/src/main/java/graphql/analysis/values/ValueVisitor.java @@ -1,6 +1,8 @@ package graphql.analysis.values; import graphql.PublicSpi; +import graphql.schema.GraphQLAppliedDirectiveArgument; +import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; @@ -122,4 +124,33 @@ interface InputElements { default @Nullable List visitListValue(@Nullable List coercedValue, GraphQLList listInputType, InputElements inputElements) { return coercedValue; } + + + /** + * This is called when a {@link GraphQLArgument} is encountered + * + * @param coercedValue the value that is in coerced form + * @param graphQLArgument the {@link GraphQLArgument} in play + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Object visitArgumentValue(@Nullable Object coercedValue, GraphQLArgument graphQLArgument, InputElements inputElements) { + return coercedValue; + } + + + /** + * This is called when a {@link GraphQLAppliedDirectiveArgument} is encountered + * + * @param coercedValue the value that is in coerced form + * @param graphQLAppliedDirectiveArgument the {@link GraphQLAppliedDirectiveArgument} in play + * @param inputElements the elements that lead to this value and type + * + * @return the same value or a new value + */ + default @Nullable Object visitAppliedDirectiveArgumentValue(@Nullable Object coercedValue, GraphQLAppliedDirectiveArgument graphQLAppliedDirectiveArgument, InputElements inputElements) { + return coercedValue; + } + } diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java index 81172a3721..6f19bbd126 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirectiveArgument.java @@ -26,7 +26,7 @@ * You can think of them as 'instances' of {@link GraphQLArgument}, when applied to a directive on a schema element */ @PublicApi -public class GraphQLAppliedDirectiveArgument implements GraphQLNamedSchemaElement { +public class GraphQLAppliedDirectiveArgument implements GraphQLNamedSchemaElement, GraphQLInputSchemaElement { private final String name; private final InputValueWithState value; diff --git a/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy b/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy index 9a29cdb827..dabb380e53 100644 --- a/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy +++ b/src/test/groovy/graphql/analysis/values/ValueTraverserTest.groovy @@ -1,11 +1,14 @@ package graphql.analysis.values +import graphql.AssertException import graphql.ExecutionInput import graphql.GraphQL import graphql.TestUtil import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment import graphql.schema.DataFetchingEnvironmentImpl +import graphql.schema.GraphQLAppliedDirectiveArgument +import graphql.schema.GraphQLArgument import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer @@ -17,6 +20,7 @@ import graphql.schema.GraphQLNamedSchemaElement import graphql.schema.GraphQLScalarType import graphql.schema.idl.SchemaDirectiveWiring import graphql.schema.idl.SchemaDirectiveWiringEnvironment +import org.jetbrains.annotations.Nullable import spock.lang.Specification import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring @@ -293,6 +297,154 @@ class ValueTraverserTest extends Specification { ] } + def "can visit arguments and change things"() { + def sdl = """ + type Query { + field(arg1 : Input!, arg2 : Input, removeArg : Input) : String + } + + input Input { + name : String + age : Int + input : Input + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def argValues = [ + arg1 : + [name: "Tess", age: 42], + arg2 : + [name: "Tom", age: 24], + removeArg: + [name: "Gone-ski", age: 99], + ] + def visitor = new ValueVisitor() { + @Override + Object visitArgumentValue(@Nullable Object coercedValue, GraphQLArgument graphQLArgument, ValueVisitor.InputElements inputElements) { + if (graphQLArgument.name == "arg2") { + return [name: "Harry Potter", age: 54] + } + if (graphQLArgument.name == "removeArg") { + return ABSENCE_SENTINEL + } + return coercedValue + } + } + when: + def actual = ValueTraverser.visitPreOrder(argValues, fieldDef, visitor) + + def expected = [ + arg1: + [name: "Tess", age: 42], + arg2: + [name: "Harry Potter", age: 54] + ] + then: + actual == expected + + + // can change a DFE arguments + when: + def startingDFE = DataFetchingEnvironmentImpl.newDataFetchingEnvironment().fieldDefinition(fieldDef).arguments(argValues).build() + def newDFE = ValueTraverser.visitPreOrder(startingDFE, visitor) + + then: + newDFE.getArguments() == expected + newDFE.getFieldDefinition() == fieldDef + + // can change a single arguments + when: + def newValues = ValueTraverser.visitPreOrder(argValues['arg2'], fieldDef.getArgument("arg2"), visitor) + + then: + newValues == [name: "Harry Potter", age: 54] + + // catches non sense states + when: + ValueTraverser.visitPreOrder([:], fieldDef.getArgument("removeArg"), visitor) + + then: + thrown(AssertException.class) + } + + def "can handle applied directive arguments"() { + def sdl = """ + directive @d( + arg1 : Input + arg2 : Input + removeArg : Input + ) on FIELD_DEFINITION + + type Query { + field : String @d( + arg1: + {name: "Tom Riddle", age: 42} + arg2: + {name: "Ron Weasley", age: 42} + removeArg: + {name: "Ron Weasley", age: 42} + ) + } + + input Input { + name : String + age : Int + input : Input + } + """ + def schema = TestUtil.schema(sdl) + + def fieldDef = schema.getObjectType("Query").getFieldDefinition("field") + def appliedDirective = fieldDef.getAppliedDirective("d") + def visitor = new ValueVisitor() { + + @Override + Object visitScalarValue(@Nullable Object coercedValue, GraphQLScalarType inputType, ValueVisitor.InputElements inputElements) { + if (coercedValue == "Tom Riddle") { + return "Happy Potter" + } + return coercedValue + } + + @Override + Object visitAppliedDirectiveArgumentValue(@Nullable Object coercedValue, GraphQLAppliedDirectiveArgument graphQLAppliedDirectiveArgument, ValueVisitor.InputElements inputElements) { + if (graphQLAppliedDirectiveArgument.name == "arg2") { + return [name: "Harry Potter", age: 54] + } + if (graphQLAppliedDirectiveArgument.name == "removeArg") { + return ABSENCE_SENTINEL + } + return coercedValue + } + } + + + def appliedDirectiveArgument = appliedDirective.getArgument("arg1") + when: + def actual = ValueTraverser.visitPreOrder(appliedDirectiveArgument.getValue(), appliedDirectiveArgument, visitor) + + then: + actual == [name: "Happy Potter", age: 42] + + when: + appliedDirectiveArgument = appliedDirective.getArgument("arg2") + actual = ValueTraverser.visitPreOrder(appliedDirectiveArgument.getValue(), appliedDirectiveArgument, visitor) + + then: + actual == [name: "Harry Potter", age: 54] + + + // catches non sense states + when: + appliedDirectiveArgument = appliedDirective.getArgument("removeArg") + ValueTraverser.visitPreOrder(appliedDirectiveArgument.getValue(), appliedDirectiveArgument, visitor) + + then: + thrown(AssertException.class) + } + def "can handle a null changes"() { def sdl = """ type Query {