@@ -398,6 +398,7 @@ class QueryPlanningTraversal<RV extends Vertex> {
398
398
initialContext : PathContext ,
399
399
typeConditionedFetching : boolean ,
400
400
nonLocalSelectionsState : NonLocalSelectionsState | null ,
401
+ initialSubgraphConstraint : string | null ,
401
402
excludedDestinations : ExcludedDestinations = [ ] ,
402
403
excludedConditions : ExcludedConditions = [ ] ,
403
404
) {
@@ -418,6 +419,7 @@ class QueryPlanningTraversal<RV extends Vertex> {
418
419
excludedDestinations ,
419
420
excludedConditions ,
420
421
parameters . overrideConditions ,
422
+ initialSubgraphConstraint ,
421
423
) ;
422
424
this . stack = mapOptionsToSelections ( selectionSet , initialOptions ) ;
423
425
if (
@@ -527,8 +529,9 @@ class QueryPlanningTraversal<RV extends Vertex> {
527
529
// If we have no options, it means there is no way to build a plan for that branch, and
528
530
// that means the whole query planning has no plan.
529
531
// This should never happen for a top-level query planning (unless the supergraph has *not* been
530
- // validated), but can happen when computing sub-plans for a key condition.
531
- if ( this . isTopLevel ) {
532
+ // validated), but can happen when computing sub-plans for a key condition and when computing
533
+ // a top-level plan for a mutation field on a specific subgraph.
534
+ if ( this . isTopLevel && this . rootKind !== 'mutation' ) {
532
535
debug . groupEnd ( ( ) => `No valid options to advance ${ selection } from ${ advanceOptionsToString ( options ) } ` ) ;
533
536
throw new Error ( `Was not able to find any options for ${ selection } : This shouldn't have happened.` ) ;
534
537
} else {
@@ -794,6 +797,7 @@ class QueryPlanningTraversal<RV extends Vertex> {
794
797
context ,
795
798
this . typeConditionedFetching ,
796
799
null ,
800
+ null ,
797
801
excludedDestinations ,
798
802
addConditionExclusion ( excludedConditions , edge . conditions ) ,
799
803
) . findBestPlan ( ) ;
@@ -3593,7 +3597,7 @@ function computePlanInternal({
3593
3597
3594
3598
const { operation, processor } = parameters ;
3595
3599
if ( operation . rootKind === 'mutation' ) {
3596
- const dependencyGraphs = computeRootSerialDependencyGraph (
3600
+ const dependencyGraphs = computeRootSerialDependencyGraphForMutation (
3597
3601
parameters ,
3598
3602
hasDefers ,
3599
3603
nonLocalSelectionsState ,
@@ -3770,13 +3774,52 @@ function computeRootParallelBestPlan(
3770
3774
emptyContext ,
3771
3775
parameters . config . typeConditionedFetching ,
3772
3776
nonLocalSelectionsState ,
3777
+ null ,
3773
3778
) ;
3774
3779
const plan = planningTraversal . findBestPlan ( ) ;
3775
3780
// Getting no plan means the query is essentially unsatisfiable (it's a valid query, but we can prove it will never return a result),
3776
3781
// so we just return an empty plan.
3777
3782
return plan ?? createEmptyPlan ( parameters ) ;
3778
3783
}
3779
3784
3785
+ function computeRootParallelBestPlanForMutation (
3786
+ parameters : PlanningParameters < RootVertex > ,
3787
+ selection : SelectionSet ,
3788
+ startFetchIdGen : number ,
3789
+ hasDefers : boolean ,
3790
+ nonLocalSelectionsState : NonLocalSelectionsState | null ,
3791
+ ) : [ FetchDependencyGraph , OpPathTree < RootVertex > , number ] {
3792
+ let bestPlan :
3793
+ | [ FetchDependencyGraph , OpPathTree < RootVertex > , number ]
3794
+ | undefined ;
3795
+ const mutationSubgraphs = parameters . federatedQueryGraph
3796
+ . outEdges ( parameters . root ) . map ( ( edge ) => edge . tail . source ) ;
3797
+ for ( const mutationSubgraph of mutationSubgraphs ) {
3798
+ const planningTraversal = new QueryPlanningTraversal (
3799
+ parameters ,
3800
+ selection ,
3801
+ startFetchIdGen ,
3802
+ hasDefers ,
3803
+ parameters . root . rootKind ,
3804
+ defaultCostFunction ,
3805
+ emptyContext ,
3806
+ parameters . config . typeConditionedFetching ,
3807
+ nonLocalSelectionsState ,
3808
+ mutationSubgraph ,
3809
+ ) ;
3810
+ const plan = planningTraversal . findBestPlan ( ) ;
3811
+ if ( ! bestPlan || ( plan && plan [ 2 ] < bestPlan [ 2 ] ) ) {
3812
+ bestPlan = plan ;
3813
+ }
3814
+ }
3815
+ if ( ! bestPlan ) {
3816
+ throw new Error (
3817
+ `Was not able to plan ${ parameters . operation . toString ( false , false ) } starting from a single subgraph: This shouldn't have happened.` ,
3818
+ ) ;
3819
+ }
3820
+ return bestPlan ;
3821
+ }
3822
+
3780
3823
function createEmptyPlan (
3781
3824
parameters : PlanningParameters < RootVertex > ,
3782
3825
) : [ FetchDependencyGraph , OpPathTree < RootVertex > , number ] {
@@ -3794,7 +3837,7 @@ function onlyRootSubgraph(graph: FetchDependencyGraph): string {
3794
3837
return subgraphs [ 0 ] ;
3795
3838
}
3796
3839
3797
- function computeRootSerialDependencyGraph (
3840
+ function computeRootSerialDependencyGraphForMutation (
3798
3841
parameters : PlanningParameters < RootVertex > ,
3799
3842
hasDefers : boolean ,
3800
3843
nonLocalSelectionsState : NonLocalSelectionsState | null ,
@@ -3805,7 +3848,7 @@ function computeRootSerialDependencyGraph(
3805
3848
const splittedRoots = splitTopLevelFields ( operation . selectionSet ) ;
3806
3849
const graphs : FetchDependencyGraph [ ] = [ ] ;
3807
3850
let startingFetchId = 0 ;
3808
- let [ prevDepGraph , prevPaths ] = computeRootParallelBestPlan (
3851
+ let [ prevDepGraph , prevPaths ] = computeRootParallelBestPlanForMutation (
3809
3852
parameters ,
3810
3853
splittedRoots [ 0 ] ,
3811
3854
startingFetchId ,
@@ -3814,7 +3857,7 @@ function computeRootSerialDependencyGraph(
3814
3857
) ;
3815
3858
let prevSubgraph = onlyRootSubgraph ( prevDepGraph ) ;
3816
3859
for ( let i = 1 ; i < splittedRoots . length ; i ++ ) {
3817
- const [ newDepGraph , newPaths ] = computeRootParallelBestPlan (
3860
+ const [ newDepGraph , newPaths ] = computeRootParallelBestPlanForMutation (
3818
3861
parameters ,
3819
3862
splittedRoots [ i ] ,
3820
3863
prevDepGraph . nextFetchId ( ) ,
0 commit comments