Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(delegate): deduplicate upstream documents #6138

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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