Skip to content

Commit f94b511

Browse files
OverlappingFieldsCanBeMergedRule: Fix performance degradation (#3958)
Co-authored-by: AaronMoat <[email protected]> Co-authored-by: Ivan Goncharov <[email protected]> Resolves #3955 (at least
1 parent d32b99d commit f94b511

File tree

3 files changed

+66
-16
lines changed

3 files changed

+66
-16
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { graphqlSync } from 'graphql/graphql.js';
2+
import { buildSchema } from 'graphql/utilities/buildASTSchema.js';
3+
4+
const schema = buildSchema('type Query { hello: String! }');
5+
const source = `{ ${'hello '.repeat(250)}}`;
6+
7+
export const benchmark = {
8+
name: 'Many repeated fields',
9+
count: 5,
10+
measure() {
11+
graphqlSync({ schema, source });
12+
},
13+
};

src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,24 @@ describe('Validate: Overlapping fields can be merged', () => {
179179
]);
180180
});
181181

182+
it('different stream directive extra argument', () => {
183+
expectErrors(`
184+
fragment conflictingArgs on Dog {
185+
name @stream(label: "streamLabel", initialCount: 1)
186+
name @stream(label: "streamLabel", initialCount: 1, extraArg: true)
187+
}
188+
`).toDeepEqual([
189+
{
190+
message:
191+
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
192+
locations: [
193+
{ line: 3, column: 9 },
194+
{ line: 4, column: 9 },
195+
],
196+
},
197+
]);
198+
});
199+
182200
it('mix of stream and no stream', () => {
183201
expectErrors(`
184202
fragment conflictingArgs on Dog {

src/validation/rules/OverlappingFieldsCanBeMergedRule.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import type {
77
DirectiveNode,
88
FieldNode,
99
FragmentDefinitionNode,
10-
ObjectValueNode,
1110
SelectionSetNode,
11+
ValueNode,
1212
} from '../../language/ast.js';
1313
import { Kind } from '../../language/kinds.js';
1414
import { print } from '../../language/printer.js';
@@ -592,7 +592,7 @@ function findConflict(
592592
}
593593

594594
// Two field calls must have the same arguments.
595-
if (stringifyArguments(node1) !== stringifyArguments(node2)) {
595+
if (!sameArguments(node1, node2)) {
596596
return [
597597
[responseName, 'they have differing arguments'],
598598
[node1],
@@ -649,19 +649,38 @@ function findConflict(
649649
}
650650
}
651651

652-
function stringifyArguments(fieldNode: FieldNode | DirectiveNode): string {
653-
// FIXME https://github.com/graphql/graphql-js/issues/2203
654-
const args = /* c8 ignore next */ fieldNode.arguments ?? [];
655-
656-
const inputObjectWithArgs: ObjectValueNode = {
657-
kind: Kind.OBJECT,
658-
fields: args.map((argNode) => ({
659-
kind: Kind.OBJECT_FIELD,
660-
name: argNode.name,
661-
value: argNode.value,
662-
})),
663-
};
664-
return print(sortValueNode(inputObjectWithArgs));
652+
function sameArguments(
653+
node1: FieldNode | DirectiveNode,
654+
node2: FieldNode | DirectiveNode,
655+
): boolean {
656+
const args1 = node1.arguments;
657+
const args2 = node2.arguments;
658+
659+
if (args1 === undefined || args1.length === 0) {
660+
return args2 === undefined || args2.length === 0;
661+
}
662+
if (args2 === undefined || args2.length === 0) {
663+
return false;
664+
}
665+
666+
if (args1.length !== args2.length) {
667+
return false;
668+
}
669+
670+
const values2 = new Map(args2.map(({ name, value }) => [name.value, value]));
671+
return args1.every((arg1) => {
672+
const value1 = arg1.value;
673+
const value2 = values2.get(arg1.name.value);
674+
if (value2 === undefined) {
675+
return false;
676+
}
677+
678+
return stringifyValue(value1) === stringifyValue(value2);
679+
});
680+
}
681+
682+
function stringifyValue(value: ValueNode): string | null {
683+
return print(sortValueNode(value));
665684
}
666685

667686
function getStreamDirective(
@@ -681,7 +700,7 @@ function sameStreams(
681700
return true;
682701
} else if (stream1 && stream2) {
683702
// check if both fields have equivalent streams
684-
return stringifyArguments(stream1) === stringifyArguments(stream2);
703+
return sameArguments(stream1, stream2);
685704
}
686705
// fields have a mix of stream and no stream
687706
return false;

0 commit comments

Comments
 (0)