Skip to content

Commit

Permalink
feat(delegate): deduplicate upstream documents
Browse files Browse the repository at this point in the history
Changeset

Fix tests

Deduplicate more

Test recursive spreads

Fix tests

Nested

Refactor

Use the new algo in more places
  • Loading branch information
ardatan committed May 7, 2024
1 parent 57374ec commit c788f54
Show file tree
Hide file tree
Showing 12 changed files with 406 additions and 108 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-camels-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-tools/delegate": patch
---

Deduplicate fields, inline fragment spreads and fragment spreads before sending the operation document to the subschema
17 changes: 10 additions & 7 deletions packages/batch-execute/src/mergeRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
VariableNode,
visit,
} from 'graphql';
import { ExecutionRequest, getOperationASTFromRequest } from '@graphql-tools/utils';
import {
ExecutionRequest,
getOperationASTFromRequest,
SelectionSetBuilder,
} from '@graphql-tools/utils';
import { createPrefix } from './prefix.js';

/**
Expand Down Expand Up @@ -60,7 +64,7 @@ export function mergeRequests(
): ExecutionRequest {
const mergedVariables: Record<string, any> = Object.create(null);
const mergedVariableDefinitions: Array<VariableDefinitionNode> = [];
const mergedSelections: Array<SelectionNode> = [];
const mergedSelections = new SelectionSetBuilder();
const mergedFragmentDefinitions: Array<FragmentDefinitionNode> = [];
let mergedExtensions: Record<string, any> = Object.create(null);

Expand All @@ -70,7 +74,9 @@ export function mergeRequests(

for (const def of prefixedRequests.document.definitions) {
if (isOperationDefinition(def)) {
mergedSelections.push(...def.selectionSet.selections);
for (const selection of def.selectionSet.selections) {
mergedSelections.addSelection(selection);
}
if (def.variableDefinitions) {
mergedVariableDefinitions.push(...def.variableDefinitions);
}
Expand All @@ -90,10 +96,7 @@ export function mergeRequests(
kind: Kind.OPERATION_DEFINITION,
operation: operationType,
variableDefinitions: mergedVariableDefinitions,
selectionSet: {
kind: Kind.SELECTION_SET,
selections: mergedSelections,
},
selectionSet: mergedSelections.getSelectionSet(),
};
const operationName = firstRequest.operationName ?? firstRequest.info?.operation?.name?.value;
if (operationName) {
Expand Down
3 changes: 1 addition & 2 deletions packages/batch-execute/tests/batchExecute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ describe('batch execution', () => {
])) as ExecutionResult[];

const squishedDoc = executorDocument?.replace(/\s+/g, ' ');
expect(squishedDoc).toMatch('... on Query { _0_field1: field1 }');
expect(squishedDoc).toMatch('... on Query { _1_field2: field2 }');
expect(squishedDoc).toMatch('... on Query { _0_field1: field1 _1_field2: field2 }');
expect(first?.data).toEqual({ field1: '1' });
expect(second?.data).toEqual({ field2: '2' });
expect(executorCalls).toEqual(1);
Expand Down
13 changes: 4 additions & 9 deletions packages/delegate/src/createRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
NameNode,
OperationDefinitionNode,
OperationTypeNode,
SelectionNode,
SelectionSetNode,
typeFromAST,
VariableDefinitionNode,
} from 'graphql';
import {
createVariableNameGenerator,
ExecutionRequest,
SelectionSetBuilder,
serializeInputValue,
updateArgument,
} from '@graphql-tools/utils';
Expand Down Expand Up @@ -59,21 +59,16 @@ export function createRequest({
if (selectionSet != null) {
newSelectionSet = selectionSet;
} else {
const selections: Array<SelectionNode> = [];
const selections = new SelectionSetBuilder();
for (const fieldNode of fieldNodes || []) {
if (fieldNode.selectionSet) {
for (const selection of fieldNode.selectionSet.selections) {
selections.push(selection);
selections.addSelection(selection);
}
}
}

newSelectionSet = selections.length
? {
kind: Kind.SELECTION_SET,
selections,
}
: undefined;
newSelectionSet = selections.getSize() ? selections.getSelectionSet() : undefined;

const args = fieldNodes?.[0]?.arguments;
if (args) {
Expand Down
15 changes: 5 additions & 10 deletions packages/delegate/src/finalizeGatewayRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
getDefinedRootType,
implementsAbstractType,
inspect,
SelectionSetBuilder,
serializeInputValue,
updateArgument,
} from '@graphql-tools/utils';
Expand Down Expand Up @@ -179,7 +180,7 @@ function addVariablesToRootFields(

const type = getDefinedRootType(targetSchema, operation.operation);

const newSelections: Array<SelectionNode> = [];
const selectionSetBuilder = new SelectionSetBuilder();

for (const selection of operation.selectionSet.selections) {
if (selection.kind === Kind.FIELD) {
Expand All @@ -198,25 +199,19 @@ function addVariablesToRootFields(
if (targetField != null) {
updateArguments(targetField, argumentNodeMap, variableDefinitionMap, newVariables, args);
}

newSelections.push({
selectionSetBuilder.addSelection({
...selection,
arguments: Object.values(argumentNodeMap),
});
} else {
newSelections.push(selection);
selectionSetBuilder.addSelection(selection);
}
}

const newSelectionSet: SelectionSetNode = {
kind: Kind.SELECTION_SET,
selections: newSelections,
};

return {
...operation,
variableDefinitions: Object.values(variableDefinitionMap),
selectionSet: newSelectionSet,
selectionSet: selectionSetBuilder.getSelectionSet(),
};
});

Expand Down
40 changes: 20 additions & 20 deletions packages/delegate/src/prepareGatewayDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
isInterfaceType,
isLeafType,
Kind,
SelectionNode,
SelectionSetNode,
TypeInfo,
visit,
Expand All @@ -22,6 +21,7 @@ import {
getRootTypeNames,
implementsAbstractType,
memoize2,
SelectionSetBuilder,
} from '@graphql-tools/utils';
import { getDocumentMetadata } from './getDocumentMetadata.js';
import { StitchingInfo } from './types.js';
Expand Down Expand Up @@ -116,7 +116,7 @@ function visitSelectionSet(
Record<string, Array<(node: FieldNode) => SelectionSetNode>>
>,
): SelectionSetNode {
const newSelections = new Set<SelectionNode>();
const selectionSetBuilder = new SelectionSetBuilder();
const maybeType = typeInfo.getParentType();

if (maybeType != null) {
Expand All @@ -126,20 +126,20 @@ function visitSelectionSet(
const fieldNodes = fieldNodesByType[parentTypeName];
if (fieldNodes) {
for (const fieldNode of fieldNodes) {
newSelections.add(fieldNode);
selectionSetBuilder.addSelection(fieldNode);
}
}

const interfaceExtensions = interfaceExtensionsMap[parentType.name];
const interfaceExtensionFields: Array<SelectionNode> = [];
const interfaceExtensionFieldsBuilder = new SelectionSetBuilder();

for (const selection of node.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
if (selection.typeCondition != null) {
const possibleTypes = possibleTypesMap[selection.typeCondition.name.value];

if (possibleTypes == null) {
newSelections.add(selection);
selectionSetBuilder.addSelection(selection);
continue;
}

Expand All @@ -149,15 +149,17 @@ function visitSelectionSet(
maybePossibleType != null &&
implementsAbstractType(schema, parentType, maybePossibleType)
) {
newSelections.add(generateInlineFragment(possibleTypeName, selection.selectionSet));
selectionSetBuilder.addSelection(
generateInlineFragment(possibleTypeName, selection.selectionSet),
);
}
}
}
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
const fragmentName = selection.name.value;

if (!fragmentReplacements[fragmentName]) {
newSelections.add(selection);
selectionSetBuilder.addSelection(selection);
continue;
}

Expand All @@ -169,7 +171,7 @@ function visitSelectionSet(
maybeReplacementType != null &&
implementsAbstractType(schema, parentType, maybeType)
) {
newSelections.add({
selectionSetBuilder.addSelection({
kind: Kind.FRAGMENT_SPREAD,
name: {
kind: Kind.NAME,
Expand All @@ -184,7 +186,7 @@ function visitSelectionSet(
const fieldNodes = fieldNodesByField[parentTypeName]?.[fieldName];
if (fieldNodes != null) {
for (const fieldNode of fieldNodes) {
newSelections.add(fieldNode);
selectionSetBuilder.addSelection(fieldNode);
}
}

Expand All @@ -194,22 +196,22 @@ function visitSelectionSet(
const selectionSet = selectionSetFn(selection);
if (selectionSet != null) {
for (const selection of selectionSet.selections) {
newSelections.add(selection);
selectionSetBuilder.addSelection(selection);
}
}
}
}

if (interfaceExtensions?.[fieldName]) {
interfaceExtensionFields.push(selection);
interfaceExtensionFieldsBuilder.addSelection(selection);
} else {
newSelections.add(selection);
selectionSetBuilder.addSelection(selection);
}
}
}

if (reversePossibleTypesMap[parentType.name]) {
newSelections.add({
selectionSetBuilder.addSelection({
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
Expand All @@ -218,23 +220,21 @@ function visitSelectionSet(
});
}

if (interfaceExtensionFields.length) {
if (interfaceExtensionFieldsBuilder.getSize()) {
const possibleTypes = possibleTypesMap[parentType.name];
if (possibleTypes != null) {
for (const possibleType of possibleTypes) {
newSelections.add(
generateInlineFragment(possibleType, {
kind: Kind.SELECTION_SET,
selections: interfaceExtensionFields,
}),
const interfaceExtensionFields = interfaceExtensionFieldsBuilder.getSelectionSet();
selectionSetBuilder.addSelection(
generateInlineFragment(possibleType, interfaceExtensionFields),
);
}
}
}

return {
...node,
selections: Array.from(newSelections),
...selectionSetBuilder.getSelectionSet(),
};
}

Expand Down
Loading

0 comments on commit c788f54

Please sign in to comment.