From 8b004462700a95b213d1cc2e9d5f831295fb6a95 Mon Sep 17 00:00:00 2001 From: Boopathi Nedunchezhiyan Date: Mon, 14 Oct 2024 14:05:55 +0200 Subject: [PATCH] more perf optimizations --- src/ast.ts | 444 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 258 insertions(+), 186 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 9e63cd4e..24143205 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -99,154 +99,188 @@ function collectFieldsImpl( previousShouldInclude: string[] = [], parentResponsePath = "" ): FieldsAndNodes { - for (const selection of selectionSet.selections) { - switch (selection.kind) { - case Kind.FIELD: { - const name = getFieldEntryKey(selection); - if (!fields[name]) { - fields[name] = []; - } - const fieldNode: JitFieldNode = selection; + interface StackItem { + selectionSet: SelectionSetNode; + parentResponsePath: string; + previousShouldInclude: string[]; + } - // the current path of the field - // This is used to generate per path skip/include code - // because the same field can be reached from different paths (e.g. fragment reuse) - const currentPath = joinSkipIncludePath( - parentResponsePath, + const stack: StackItem[] = []; - // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias - name - ); + stack.push({ + selectionSet, + parentResponsePath, + previousShouldInclude + }); + + while (stack.length > 0) { + const { selectionSet, parentResponsePath, previousShouldInclude } = + stack.pop()!; + + for (const selection of selectionSet.selections) { + switch (selection.kind) { + case Kind.FIELD: { + collectFieldsForField({ + compilationContext, + fields, + parentResponsePath, + previousShouldInclude, + selection + }); + break; + } - // `should include`s generated for the current fieldNode - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + case Kind.INLINE_FRAGMENT: { + if ( + !doesFragmentConditionMatch( + compilationContext, + selection, + runtimeType + ) + ) { + continue; + } - /** - * Carry over fragment's skip and include code - * - * fieldNode.__internalShouldInclude - * --------------------------------- - * When the parent field has a skip or include, the current one - * should be skipped if the parent is skipped in the path. - * - * previousShouldInclude - * --------------------- - * `should include`s from fragment spread and inline fragments - * - * compileSkipInclude(selection) - * ----------------------------- - * `should include`s generated for the current fieldNode - */ - if (compilationContext.options.useExperimentalPathBasedSkipInclude) { - if (!fieldNode.__internalShouldIncludePath) - fieldNode.__internalShouldIncludePath = {}; - - fieldNode.__internalShouldIncludePath[currentPath] = - joinShouldIncludeCompilations( - fieldNode.__internalShouldIncludePath?.[currentPath] ?? [], + // current fragment's shouldInclude + const compiledSkipInclude = compileSkipInclude( + compilationContext, + selection + ); + + // push to stack + stack.push({ + selectionSet: selection.selectionSet, + parentResponsePath: parentResponsePath, + previousShouldInclude: joinShouldIncludeCompilations( + // `should include`s from previous fragments previousShouldInclude, + // current fragment's shouldInclude [compiledSkipInclude] - ); - } else { - // @deprecated - fieldNode.__internalShouldInclude = joinShouldIncludeCompilations( - fieldNode.__internalShouldInclude ?? [], - previousShouldInclude, - [compiledSkipInclude] - ); + ) + }); + break; } - /** - * We augment the entire subtree as the parent object's skip/include - * directives influence the child even if the child doesn't have - * skip/include on it's own. - * - * Refer the function definition for example. - */ - augmentFieldNodeTree(compilationContext, fieldNode, currentPath); - - fields[name].push(fieldNode); - break; - } - case Kind.INLINE_FRAGMENT: { - if ( - !doesFragmentConditionMatch( + case Kind.FRAGMENT_SPREAD: { + const fragName = selection.name.value; + if (visitedFragmentNames[fragName]) { + continue; + } + visitedFragmentNames[fragName] = true; + const fragment = compilationContext.fragments[fragName]; + if ( + !fragment || + !doesFragmentConditionMatch( + compilationContext, + fragment, + runtimeType + ) + ) { + continue; + } + + // current fragment's shouldInclude + const compiledSkipInclude = compileSkipInclude( compilationContext, - selection, - runtimeType - ) - ) { - continue; + selection + ); + + // push to stack + stack.push({ + selectionSet: fragment.selectionSet, + parentResponsePath, + previousShouldInclude: joinShouldIncludeCompilations( + // `should include`s from previous fragments + previousShouldInclude, + // current fragment's shouldInclude + [compiledSkipInclude] + ) + }); + break; } + } + } + } - // current fragment's shouldInclude - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + return fields; +} - // recurse - collectFieldsImpl( - compilationContext, - runtimeType, - selection.selectionSet, - fields, - visitedFragmentNames, - joinShouldIncludeCompilations( - // `should include`s from previous fragments - previousShouldInclude, - // current fragment's shouldInclude - [compiledSkipInclude] - ), - parentResponsePath - ); - break; - } +function collectFieldsForField({ + compilationContext, + fields, + parentResponsePath, + previousShouldInclude, + selection +}: { + compilationContext: CompilationContext; + fields: FieldsAndNodes; + parentResponsePath: string; + previousShouldInclude: string[]; + selection: FieldNode; +}) { + const name = getFieldEntryKey(selection); + if (!fields[name]) { + fields[name] = []; + } + const fieldNode: JitFieldNode = selection; - case Kind.FRAGMENT_SPREAD: { - const fragName = selection.name.value; - if (visitedFragmentNames[fragName]) { - continue; - } - visitedFragmentNames[fragName] = true; - const fragment = compilationContext.fragments[fragName]; - if ( - !fragment || - !doesFragmentConditionMatch(compilationContext, fragment, runtimeType) - ) { - continue; - } + // the current path of the field + // This is used to generate per path skip/include code + // because the same field can be reached from different paths (e.g. fragment reuse) + const currentPath = joinSkipIncludePath( + parentResponsePath, - // current fragment's shouldInclude - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias + name + ); - // recurse - collectFieldsImpl( - compilationContext, - runtimeType, - fragment.selectionSet, - fields, - visitedFragmentNames, - joinShouldIncludeCompilations( - // `should include`s from previous fragments - previousShouldInclude, - // current fragment's shouldInclude - [compiledSkipInclude] - ), - parentResponsePath - ); + // `should include`s generated for the current fieldNode + const compiledSkipInclude = compileSkipInclude(compilationContext, selection); - break; - } - } + /** + * Carry over fragment's skip and include code + * + * fieldNode.__internalShouldInclude + * --------------------------------- + * When the parent field has a skip or include, the current one + * should be skipped if the parent is skipped in the path. + * + * previousShouldInclude + * --------------------- + * `should include`s from fragment spread and inline fragments + * + * compileSkipInclude(selection) + * ----------------------------- + * `should include`s generated for the current fieldNode + */ + if (compilationContext.options.useExperimentalPathBasedSkipInclude) { + if (!fieldNode.__internalShouldIncludePath) + fieldNode.__internalShouldIncludePath = {}; + + fieldNode.__internalShouldIncludePath[currentPath] = + joinShouldIncludeCompilations( + fieldNode.__internalShouldIncludePath?.[currentPath] ?? [], + previousShouldInclude, + [compiledSkipInclude] + ); + } else { + // @deprecated + fieldNode.__internalShouldInclude = joinShouldIncludeCompilations( + fieldNode.__internalShouldInclude ?? [], + previousShouldInclude, + [compiledSkipInclude] + ); } - return fields; + /** + * We augment the entire subtree as the parent object's skip/include + * directives influence the child even if the child doesn't have + * skip/include on it's own. + * + * Refer the function definition for example. + */ + augmentFieldNodeTree(compilationContext, fieldNode, currentPath); + + fields[name].push(fieldNode); } /** @@ -303,66 +337,99 @@ function augmentFieldNodeTree( parentResponsePath: string ) { for (const selection of rootFieldNode.selectionSet?.selections ?? []) { - handle(rootFieldNode, selection, false, parentResponsePath); - } + /** + * Traverse through sub-selection and combine `shouldInclude`s + * from parent and current ones. + */ + interface StackItem { + parentFieldNode: JitFieldNode; + selection: SelectionNode; + comesFromFragmentSpread: boolean; + parentResponsePath: string; + } - /** - * Recursively traverse through sub-selection and combine `shouldInclude`s - * from parent and current ones. - */ - function handle( - parentFieldNode: JitFieldNode, - selection: SelectionNode, - comesFromFragmentSpread = false, - parentResponsePath: string - ) { - switch (selection.kind) { - case Kind.FIELD: { - const jitFieldNode: JitFieldNode = selection; - const currentPath = joinSkipIncludePath( - parentResponsePath, - - // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias - getFieldEntryKey(jitFieldNode) - ); + const stack: StackItem[] = []; - if (!comesFromFragmentSpread) { - if (compilationContext.options.useExperimentalPathBasedSkipInclude) { - if (!jitFieldNode.__internalShouldIncludePath) - jitFieldNode.__internalShouldIncludePath = {}; - - jitFieldNode.__internalShouldIncludePath[currentPath] = - joinShouldIncludeCompilations( - parentFieldNode.__internalShouldIncludePath?.[ - parentResponsePath - ] ?? [], - jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? [] - ); - } else { - // @deprecated - jitFieldNode.__internalShouldInclude = - joinShouldIncludeCompilations( - parentFieldNode.__internalShouldInclude ?? [], - jitFieldNode.__internalShouldInclude ?? [] - ); + stack.push({ + parentFieldNode: rootFieldNode, + selection, + comesFromFragmentSpread: false, + parentResponsePath + }); + + while (stack.length > 0) { + const { + parentFieldNode, + selection, + comesFromFragmentSpread, + parentResponsePath + } = stack.pop()!; + + switch (selection.kind) { + case Kind.FIELD: { + const jitFieldNode: JitFieldNode = selection; + const currentPath = joinSkipIncludePath( + parentResponsePath, + + // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias + getFieldEntryKey(jitFieldNode) + ); + + if (!comesFromFragmentSpread) { + if ( + compilationContext.options.useExperimentalPathBasedSkipInclude + ) { + if (!jitFieldNode.__internalShouldIncludePath) + jitFieldNode.__internalShouldIncludePath = {}; + + jitFieldNode.__internalShouldIncludePath[currentPath] = + joinShouldIncludeCompilations( + parentFieldNode.__internalShouldIncludePath?.[ + parentResponsePath + ] ?? [], + jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? [] + ); + } else { + // @deprecated + jitFieldNode.__internalShouldInclude = + joinShouldIncludeCompilations( + parentFieldNode.__internalShouldInclude ?? [], + jitFieldNode.__internalShouldInclude ?? [] + ); + } } + // go further down the query tree + for (const selection of jitFieldNode.selectionSet?.selections ?? []) { + stack.push({ + parentFieldNode: jitFieldNode, + selection, + comesFromFragmentSpread: false, + parentResponsePath: currentPath + }); + } + break; } - // go further down the query tree - for (const selection of jitFieldNode.selectionSet?.selections ?? []) { - handle(jitFieldNode, selection, false, currentPath); - } - break; - } - case Kind.INLINE_FRAGMENT: { - for (const subSelection of selection.selectionSet.selections) { - handle(parentFieldNode, subSelection, true, parentResponsePath); + case Kind.INLINE_FRAGMENT: { + for (const subSelection of selection.selectionSet.selections) { + stack.push({ + parentFieldNode, + selection: subSelection, + comesFromFragmentSpread: true, + parentResponsePath + }); + } + break; } - break; - } - case Kind.FRAGMENT_SPREAD: { - const fragment = compilationContext.fragments[selection.name.value]; - for (const subSelection of fragment.selectionSet.selections) { - handle(parentFieldNode, subSelection, true, parentResponsePath); + case Kind.FRAGMENT_SPREAD: { + const fragment = compilationContext.fragments[selection.name.value]; + for (const subSelection of fragment.selectionSet.selections) { + stack.push({ + parentFieldNode, + selection: subSelection, + comesFromFragmentSpread: true, + parentResponsePath + }); + } } } } @@ -427,6 +494,11 @@ function compileSkipInclude( compilationContext: CompilationContext, node: SelectionNode ): string { + // minor optimization to avoid compilation if there are no directives + if (node.directives == null || node.directives.length < 1) { + return "true"; + } + const { skipValue, includeValue } = compileSkipIncludeDirectiveValues( compilationContext, node