From e0941f75b1bce002bf416403df85336b05412675 Mon Sep 17 00:00:00 2001 From: Aaron Moat <2937187+AaronMoat@users.noreply.github.com> Date: Wed, 6 Sep 2023 08:29:04 +1000 Subject: [PATCH 1/2] OverlappingFieldsCanBeMergedRule: Fix performance degradation --- benchmark/repeated-fields-benchmark.js | 15 ++++++ .../rules/OverlappingFieldsCanBeMergedRule.ts | 50 +++++++++++++------ 2 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 benchmark/repeated-fields-benchmark.js diff --git a/benchmark/repeated-fields-benchmark.js b/benchmark/repeated-fields-benchmark.js new file mode 100644 index 0000000000..7dd5b179b7 --- /dev/null +++ b/benchmark/repeated-fields-benchmark.js @@ -0,0 +1,15 @@ +'use strict'; + +const { graphqlSync } = require('graphql/graphql.js'); +const { buildSchema } = require('graphql/utilities/buildASTSchema.js'); + +const schema = buildSchema('type Query { hello: String! }'); +const source = `{ ${'hello '.repeat(250)}}`; + +module.exports = { + name: 'Many repeated fields', + count: 5, + measure() { + graphqlSync({ schema, source }); + }, +}; diff --git a/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts b/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts index bdf6eb874e..35f2360946 100644 --- a/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts +++ b/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts @@ -5,10 +5,11 @@ import type { ObjMap } from '../../jsutils/ObjMap'; import { GraphQLError } from '../../error/GraphQLError'; import type { + DirectiveNode, FieldNode, FragmentDefinitionNode, - ObjectValueNode, SelectionSetNode, + ValueNode, } from '../../language/ast'; import { Kind } from '../../language/kinds'; import { print } from '../../language/printer'; @@ -588,7 +589,7 @@ function findConflict( } // Two field calls must have the same arguments. - if (stringifyArguments(node1) !== stringifyArguments(node2)) { + if (!sameArguments(node1, node2)) { return [ [responseName, 'they have differing arguments'], [node1], @@ -634,19 +635,38 @@ function findConflict( } } -function stringifyArguments(fieldNode: FieldNode): string { - // FIXME https://github.com/graphql/graphql-js/issues/2203 - const args = /* c8 ignore next */ fieldNode.arguments ?? []; - - const inputObjectWithArgs: ObjectValueNode = { - kind: Kind.OBJECT, - fields: args.map((argNode) => ({ - kind: Kind.OBJECT_FIELD, - name: argNode.name, - value: argNode.value, - })), - }; - return print(sortValueNode(inputObjectWithArgs)); +function sameArguments( + node1: FieldNode | DirectiveNode, + node2: FieldNode | DirectiveNode, +): boolean { + const args1 = node1.arguments; + const args2 = node2.arguments; + + if (args1 === undefined || args1.length === 0) { + return args2 === undefined || args2.length === 0; + } + if (args2 === undefined || args2.length === 0) { + return false; + } + + if (args1.length !== args2.length) { + return false; + } + + const values2 = new Map(args2.map(({ name, value }) => [name.value, value])); + return args1.every((arg1) => { + const value1 = arg1.value; + const value2 = values2.get(arg1.name.value); + if (value2 === undefined) { + return false; + } + + return stringifyValue(value1) === stringifyValue(value2); + }); +} + +function stringifyValue(value: ValueNode): string | null { + return print(sortValueNode(value)); } // Two types conflict if both types could not apply to a value simultaneously. From 3c127868af69b2fc43d06b89d769bf4be963c36b Mon Sep 17 00:00:00 2001 From: Aaron Moat <2937187+AaronMoat@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:19:54 +1000 Subject: [PATCH 2/2] Add coverage ignore comment --- src/validation/rules/OverlappingFieldsCanBeMergedRule.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts b/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts index 35f2360946..4305064a6f 100644 --- a/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts +++ b/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts @@ -649,8 +649,11 @@ function sameArguments( return false; } + /* c8 ignore next */ if (args1.length !== args2.length) { + /* c8 ignore next */ return false; + /* c8 ignore next */ } const values2 = new Map(args2.map(({ name, value }) => [name.value, value]));