From 308647ba3f2cabf24c1af30dd6ded28c637bfb5d Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Fri, 1 Nov 2024 05:38:17 +0200 Subject: [PATCH] polish: refactor execution plan functions to match spec (#4273) moves simplest case inside the execute[Root|Sub]ExecutionPlan() functions to better match proposed spec edits orders function in call order, moving executeSubExecutionPlan down closer to where it is used no significant change in performance ![image](https://github.com/user-attachments/assets/221a2696-fd0b-4b53-8519-eb2376327639) --- src/execution/execute.ts | 272 +++++++++++++++++++++++---------------- 1 file changed, 158 insertions(+), 114 deletions(-) diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 5e2f718176..83b274a08c 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -339,7 +339,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( ); } - const collectedFields = collectFields( + const { groupedFieldSet, newDeferUsages } = collectFields( schema, fragments, variableValues, @@ -348,24 +348,14 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( hideSuggestions, ); - const { groupedFieldSet, newDeferUsages } = collectedFields; - const graphqlWrappedResult = - newDeferUsages.length === 0 - ? executeRootGroupedFieldSet( - exeContext, - operation.operation, - rootType, - rootValue, - groupedFieldSet, - undefined, - ) - : executeExecutionPlan( - exeContext, - rootType, - rootValue, - newDeferUsages, - buildExecutionPlan(groupedFieldSet), - ); + const graphqlWrappedResult = executeRootExecutionPlan( + exeContext, + operation.operation, + rootType, + rootValue, + groupedFieldSet, + newDeferUsages, + ); if (isPromise(graphqlWrappedResult)) { return graphqlWrappedResult.then( @@ -388,78 +378,6 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( } } -function executeExecutionPlan( - exeContext: ExecutionContext, - returnType: GraphQLObjectType, - sourceValue: unknown, - newDeferUsages: ReadonlyArray, - executionPlan: ExecutionPlan, - path?: Path | undefined, - incrementalContext?: IncrementalContext | undefined, - deferMap?: ReadonlyMap | undefined, -): PromiseOrValue>> { - const newDeferMap = getNewDeferMap(newDeferUsages, deferMap, path); - - const { groupedFieldSet, newGroupedFieldSets } = executionPlan; - - const graphqlWrappedResult = executeFields( - exeContext, - returnType, - sourceValue, - path, - groupedFieldSet, - incrementalContext, - newDeferMap, - ); - - if (newGroupedFieldSets.size > 0) { - const newPendingExecutionGroups = collectExecutionGroups( - exeContext, - returnType, - sourceValue, - path, - incrementalContext?.deferUsageSet, - newGroupedFieldSets, - newDeferMap, - ); - - return withNewExecutionGroups( - graphqlWrappedResult, - newPendingExecutionGroups, - ); - } - return graphqlWrappedResult; -} - -function withNewExecutionGroups( - result: PromiseOrValue>>, - newPendingExecutionGroups: ReadonlyArray, -): PromiseOrValue>> { - if (isPromise(result)) { - return result.then((resolved) => { - addIncrementalDataRecords(resolved, newPendingExecutionGroups); - return resolved; - }); - } - - addIncrementalDataRecords(result, newPendingExecutionGroups); - return result; -} - -function addIncrementalDataRecords( - graphqlWrappedResult: GraphQLWrappedResult, - incrementalDataRecords: ReadonlyArray | undefined, -): void { - if (incrementalDataRecords === undefined) { - return; - } - if (graphqlWrappedResult.incrementalDataRecords === undefined) { - graphqlWrappedResult.incrementalDataRecords = [...incrementalDataRecords]; - } else { - graphqlWrappedResult.incrementalDataRecords.push(...incrementalDataRecords); - } -} - function withError( errors: Array | undefined, error: GraphQLError, @@ -616,6 +534,73 @@ export function validateExecutionArgs( }; } +function executeRootExecutionPlan( + exeContext: ExecutionContext, + operation: OperationTypeNode, + rootType: GraphQLObjectType, + rootValue: unknown, + originalGroupedFieldSet: GroupedFieldSet, + newDeferUsages: ReadonlyArray, +): PromiseOrValue>> { + if (newDeferUsages.length === 0) { + return executeRootGroupedFieldSet( + exeContext, + operation, + rootType, + rootValue, + originalGroupedFieldSet, + undefined, + ); + } + const newDeferMap = getNewDeferMap(newDeferUsages, undefined, undefined); + + const { groupedFieldSet, newGroupedFieldSets } = buildExecutionPlan( + originalGroupedFieldSet, + ); + + const graphqlWrappedResult = executeRootGroupedFieldSet( + exeContext, + operation, + rootType, + rootValue, + groupedFieldSet, + newDeferMap, + ); + + if (newGroupedFieldSets.size > 0) { + const newPendingExecutionGroups = collectExecutionGroups( + exeContext, + rootType, + rootValue, + undefined, + undefined, + newGroupedFieldSets, + newDeferMap, + ); + + return withNewExecutionGroups( + graphqlWrappedResult, + newPendingExecutionGroups, + ); + } + return graphqlWrappedResult; +} + +function withNewExecutionGroups( + result: PromiseOrValue>>, + newPendingExecutionGroups: ReadonlyArray, +): PromiseOrValue>> { + if (isPromise(result)) { + return result.then((resolved) => { + addIncrementalDataRecords(resolved, newPendingExecutionGroups); + return resolved; + }); + } + + addIncrementalDataRecords(result, newPendingExecutionGroups); + return result; +} + function executeRootGroupedFieldSet( exeContext: ExecutionContext, operation: OperationTypeNode, @@ -728,6 +713,20 @@ function executeFieldsSerially( ); } +function addIncrementalDataRecords( + graphqlWrappedResult: GraphQLWrappedResult, + incrementalDataRecords: ReadonlyArray | undefined, +): void { + if (incrementalDataRecords === undefined) { + return; + } + if (graphqlWrappedResult.incrementalDataRecords === undefined) { + graphqlWrappedResult.incrementalDataRecords = [...incrementalDataRecords]; + } else { + graphqlWrappedResult.incrementalDataRecords.push(...incrementalDataRecords); + } +} + /** * Implements the "Executing selection sets" section of the spec * for fields that may be executed in parallel. @@ -1877,29 +1876,74 @@ function collectAndExecuteSubfields( fieldDetailsList, ); const { groupedFieldSet, newDeferUsages } = collectedSubfields; - return deferMap === undefined && newDeferUsages.length === 0 - ? executeFields( - exeContext, - returnType, - result, - path, - groupedFieldSet, - incrementalContext, - undefined, - ) - : executeExecutionPlan( - exeContext, - returnType, - result, - newDeferUsages, - buildSubExecutionPlan( - groupedFieldSet, - incrementalContext?.deferUsageSet, - ), - path, - incrementalContext, - deferMap, - ); + return executeSubExecutionPlan( + exeContext, + returnType, + result, + groupedFieldSet, + newDeferUsages, + path, + incrementalContext, + deferMap, + ); +} + +function executeSubExecutionPlan( + exeContext: ExecutionContext, + returnType: GraphQLObjectType, + sourceValue: unknown, + originalGroupedFieldSet: GroupedFieldSet, + newDeferUsages: ReadonlyArray, + path?: Path | undefined, + incrementalContext?: IncrementalContext | undefined, + deferMap?: ReadonlyMap | undefined, +): PromiseOrValue>> { + if (deferMap === undefined && newDeferUsages.length === 0) { + return executeFields( + exeContext, + returnType, + sourceValue, + path, + originalGroupedFieldSet, + incrementalContext, + deferMap, + ); + } + + const newDeferMap = getNewDeferMap(newDeferUsages, deferMap, path); + + const { groupedFieldSet, newGroupedFieldSets } = buildSubExecutionPlan( + originalGroupedFieldSet, + incrementalContext?.deferUsageSet, + ); + + const graphqlWrappedResult = executeFields( + exeContext, + returnType, + sourceValue, + path, + groupedFieldSet, + incrementalContext, + newDeferMap, + ); + + if (newGroupedFieldSets.size > 0) { + const newPendingExecutionGroups = collectExecutionGroups( + exeContext, + returnType, + sourceValue, + path, + incrementalContext?.deferUsageSet, + newGroupedFieldSets, + newDeferMap, + ); + + return withNewExecutionGroups( + graphqlWrappedResult, + newPendingExecutionGroups, + ); + } + return graphqlWrappedResult; } function buildSubExecutionPlan(