All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.execution.nextgen.BatchedExecutionStrategy Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
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;

@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;
    }
}