-
Notifications
You must be signed in to change notification settings - Fork 31
Defer query visitor #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Defer query visitor #154
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9c8f3e3
Adding a query traversal for defer
kmoore-intuit a4003f6
add documentation for visitors
kmoore-intuit 8af6686
pr fixes and refactor for ease of use
kmoore-intuit e0affa3
test for pruning child selections
kmoore-intuit f4af2f3
pr fixes
kmoore-intuit bfc702e
initial query traveral using query transformer
kmoore-intuit d98a1f1
pr fixes
kmoore-intuit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/java/com/intuit/graphql/orchestrator/deferDirective/DeferOptions.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.intuit.graphql.orchestrator.deferDirective; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| @Builder | ||
| @Getter | ||
| public class DeferOptions { | ||
| private boolean nestedDefersAllowed; | ||
| } |
53 changes: 53 additions & 0 deletions
53
src/main/java/com/intuit/graphql/orchestrator/deferDirective/DeferUtil.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.intuit.graphql.orchestrator.deferDirective; | ||
|
|
||
| import graphql.language.Argument; | ||
| import graphql.language.BooleanValue; | ||
| import graphql.language.Directive; | ||
| import graphql.language.DirectivesContainer; | ||
| import graphql.language.Node; | ||
| import graphql.language.Selection; | ||
| import graphql.language.SelectionSet; | ||
| import lombok.NonNull; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static com.intuit.graphql.orchestrator.utils.DirectivesUtil.DEFER_DIRECTIVE_NAME; | ||
| import static com.intuit.graphql.orchestrator.utils.DirectivesUtil.DEFER_IF_ARG; | ||
|
|
||
| public class DeferUtil { | ||
| /** | ||
| * Checks if it is necessary to create ei for deferred field. | ||
| * Currently, selection should be skipped if all the children field are deferred resulting in an empty selection set. | ||
| * @param selection: node to check if children are all deferred | ||
| * @return boolean: true if all children are deferred, false otherwise | ||
| */ | ||
| public static boolean hasNonDeferredSelection(@NonNull Selection selection) { | ||
| return ((List<Node>)selection.getChildren()) | ||
| .stream() | ||
| .filter(SelectionSet.class::isInstance) | ||
| .map(SelectionSet.class::cast) | ||
| .findAny() | ||
| .get() | ||
| .getSelections() | ||
| .stream() | ||
| .anyMatch(child -> !containsEnabledDeferDirective(child)); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that Node has defer directive that is not disabled | ||
| * @param node: node to check if contains defer directive | ||
| * @return boolean: true if node has an enabled defer, false otherwise | ||
| */ | ||
| public static boolean containsEnabledDeferDirective(Selection node) { | ||
| return node instanceof DirectivesContainer && | ||
| ((List<Directive>) ((DirectivesContainer) node).getDirectives()) | ||
| .stream() | ||
| .filter(directive -> DEFER_DIRECTIVE_NAME.equals(directive.getName())) | ||
| .findFirst() | ||
| .map(directive -> { | ||
| Argument ifArg = directive.getArgument(DEFER_IF_ARG); | ||
| return ifArg == null || ((BooleanValue) ifArg.getValue()).isValue(); | ||
| }) | ||
| .orElse(false); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,41 @@ | ||
| package com.intuit.graphql.orchestrator.utils; | ||
|
|
||
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.intuit.graphql.orchestrator.deferDirective.DeferOptions; | ||
| import com.intuit.graphql.orchestrator.visitors.queryVisitors.DeferQueryExtractor; | ||
| import graphql.ExecutionInput; | ||
| import graphql.analysis.QueryTransformer; | ||
| import graphql.language.Document; | ||
| import graphql.language.FragmentDefinition; | ||
| import graphql.language.OperationDefinition; | ||
| import graphql.language.SelectionSet; | ||
| import graphql.schema.GraphQLSchema; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import reactor.core.publisher.Flux; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static com.intuit.graphql.orchestrator.utils.GraphQLUtil.parser; | ||
|
|
||
| @Slf4j | ||
| public class MultiEIGenerator { | ||
|
|
||
| private List<ExecutionInput> eis = new ArrayList<>(); | ||
| private final List<ExecutionInput> eis = new ArrayList<>(); | ||
| private final DeferOptions deferOptions; | ||
| private final GraphQLSchema schema; | ||
| private Integer numOfEIs = null; | ||
| private final static String EMPTY_QUERY =""; | ||
|
|
||
| @VisibleForTesting | ||
| private long timeProcessedSplit = 0; | ||
|
|
||
| public MultiEIGenerator(ExecutionInput ei) { | ||
| public MultiEIGenerator(ExecutionInput ei, DeferOptions deferOptions, GraphQLSchema schema) { | ||
| this.eis.add(ei); | ||
| this.deferOptions = deferOptions; | ||
| this.schema = schema; | ||
| } | ||
|
|
||
| public Flux<ExecutionInput> generateEIs() { | ||
|
|
@@ -39,7 +55,40 @@ public Flux<ExecutionInput> generateEIs() { | |
| this.timeProcessedSplit = System.currentTimeMillis(); | ||
| //Adds elements to list of eis that need to be processed | ||
| try { | ||
| this.eis.addAll(MultipartUtil.splitMultipartExecutionInput(emittedEI)); | ||
| Document rootDocument = parser.parseDocument(emittedEI.getQuery()); | ||
| Map<String, FragmentDefinition> fragmentDefinitionMap = rootDocument.getDefinitionsOfType(FragmentDefinition.class) | ||
| .stream() | ||
| .collect(Collectors.toMap(FragmentDefinition::getName , Function.identity())); | ||
|
|
||
| ExecutionInput finalEmittedEI = emittedEI; | ||
| AtomicReference<OperationDefinition> operationDefinitionReference = new AtomicReference<>(); | ||
| rootDocument.getDefinitionsOfType(OperationDefinition.class) | ||
| .stream() | ||
| .peek(operationDefinitionReference::set) | ||
| .map(OperationDefinition::getSelectionSet) | ||
| .map(SelectionSet::getSelections) | ||
| .flatMap(List::stream) | ||
| .forEach(selection -> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sequence looks sweet |
||
| QueryTransformer transformer = QueryTransformer.newQueryTransformer() | ||
| .schema(this.schema) | ||
| .root(selection) | ||
| .rootParentType(this.schema.getQueryType()) | ||
| .fragmentsByName(fragmentDefinitionMap) | ||
| .variables(finalEmittedEI.getVariables()) | ||
| .build(); | ||
|
|
||
| DeferQueryExtractor visitor = DeferQueryExtractor.builder() | ||
| .deferOptions(deferOptions) | ||
| .originalEI(finalEmittedEI) | ||
| .rootNode(rootDocument) | ||
| .operationDefinition(operationDefinitionReference.get()) | ||
| .fragmentDefinitionMap(fragmentDefinitionMap) | ||
| .build(); | ||
|
|
||
| transformer.transform(visitor); | ||
|
|
||
| this.eis.addAll(visitor.getExtractedEIs()); | ||
| }); | ||
| } | ||
| catch (Exception ex) { | ||
| sink.error(ex); | ||
|
|
||
98 changes: 98 additions & 0 deletions
98
src/main/java/com/intuit/graphql/orchestrator/utils/NodeUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| package com.intuit.graphql.orchestrator.utils; | ||
|
|
||
| import graphql.language.Directive; | ||
| import graphql.language.DirectivesContainer; | ||
| import graphql.language.Field; | ||
| import graphql.language.FragmentDefinition; | ||
| import graphql.language.FragmentSpread; | ||
| import graphql.language.InlineFragment; | ||
| import graphql.language.Node; | ||
| import graphql.language.OperationDefinition; | ||
| import graphql.language.Selection; | ||
| import graphql.language.SelectionSet; | ||
| import org.apache.commons.lang3.ObjectUtils; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class NodeUtils { | ||
|
|
||
| /** | ||
| * Transforms node by removing desired directive from the node. | ||
| * Returns transformed node | ||
| * Throws an exception if it is at a location that is not supported. | ||
| * @param node: the selection that you would like to prune | ||
| * @param directiveName: the directive you would like to search for | ||
| * @return selection: pruned selection | ||
| * */ | ||
| public static Selection removeDirectiveFromNode(Selection node, String directiveName) { | ||
| //DirectivesContainer returns getDirectives as a List<Object> so need to cast to stream correctly | ||
| List<Directive> prunedDirectives = ((List<Directive>)((DirectivesContainer)node).getDirectives()) | ||
| .stream() | ||
| .filter(directive -> ObjectUtils.notEqual(directiveName, directive.getName())) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| if(node instanceof Field) { | ||
| return ((Field) node).transform(builder -> builder.directives(prunedDirectives)); | ||
| } else if(node instanceof InlineFragment) { | ||
| return ((InlineFragment) node).transform(builder -> builder.directives(prunedDirectives)); | ||
| } else { | ||
| return ((FragmentSpread) node).transform(builder -> builder.directives(prunedDirectives)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if the selection has children selections | ||
| * @param node selection that you are trying to check | ||
| * @return boolean: true if selection doesn't have selectionset as child, false otherwise | ||
| * */ | ||
| public static boolean isLeaf(Selection node) { | ||
| return node.getChildren().isEmpty() || | ||
| node.getChildren() | ||
| .stream() | ||
| .noneMatch(SelectionSet.class::isInstance); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Generates new node | ||
| * Transforms the parentNode with a new selection set consisting of the pruned child and typename fields | ||
| * @param parentNode node that will be transformed and will contain only selections | ||
| * @param selections selections that need to be in selection set for parentNode | ||
| * @return node that only contains the passed in selections | ||
| * */ | ||
| public static Node transformNodeWithSelections(Node parentNode, Selection... selections) { | ||
| SelectionSet prunedSelectionSet = SelectionSet.newSelectionSet() | ||
| .selections(Arrays.asList(selections)) | ||
| .build(); | ||
|
|
||
| if(parentNode instanceof Field) { | ||
| return ((Field) parentNode).transform(builder -> builder.selectionSet(prunedSelectionSet)); | ||
| } else if (parentNode instanceof FragmentDefinition) { | ||
| //add fragment spread names here in case of nested fragment spreads | ||
| return ((FragmentDefinition) parentNode).transform(builder -> builder.selectionSet(prunedSelectionSet)); | ||
| } else if (parentNode instanceof InlineFragment) { | ||
| return ((InlineFragment) parentNode).transform(builder -> builder.selectionSet(prunedSelectionSet)); | ||
| } else { | ||
| return ((OperationDefinition) parentNode).transform(builder -> builder.selectionSet(prunedSelectionSet)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the objects from map matching key | ||
| * @param kvMap: map that will be searched | ||
| * @param keyCollection keys to check | ||
| * @return list of values from map with matching key | ||
| * */ | ||
| public static <K, V> List<V> getAllMapValuesWithMatchingKeys(Map<K, V> kvMap, Collection<K> keyCollection) { | ||
| return keyCollection | ||
| .stream() | ||
| .map(kvMap::get) | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/main/java/com/intuit/graphql/orchestrator/utils/TraverserContextUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.intuit.graphql.orchestrator.utils; | ||
|
|
||
| import graphql.language.Node; | ||
| import graphql.language.SelectionSetContainer; | ||
| import graphql.util.TraverserContext; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class TraverserContextUtils { | ||
| /** | ||
| * Returns the current nodes parent node definitions | ||
| * @param currentNode context for current node | ||
| * @return List of graphql definitions | ||
| * */ | ||
| public static List getParentDefinitions(TraverserContext<Node> currentNode) { | ||
| return currentNode.getParentNodes() | ||
| .stream() | ||
| .filter(SelectionSetContainer.class::isInstance) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.