Skip to content

Commit 11447b5

Browse files
committed
switch from using a TaggedFieldNode to a map of deferDepth to Array<FieldNode>
1 parent 9dbb871 commit 11447b5

File tree

5 files changed

+66
-74
lines changed

5 files changed

+66
-74
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,22 +230,26 @@ describe('Execute: Handles basic execution tasks', () => {
230230
const operation = document.definitions[0];
231231
assert(operation.kind === Kind.OPERATION_DEFINITION);
232232

233-
expect(resolvedInfo).to.include({
233+
expect(resolvedInfo).to.deep.include({
234234
fieldName: 'test',
235235
returnType: GraphQLString,
236236
parentType: testType,
237237
schema,
238238
rootValue,
239239
operation,
240-
});
241-
242-
const fieldNode = operation.selectionSet.selections[0];
243-
expect(resolvedInfo).to.deep.include({
244-
fieldGroup: { depth: 0, fields: [{ fieldNode, deferDepth: undefined }] },
245240
deferDepth: undefined,
246241
variableValues: { var: 'abc' },
247242
});
248243

244+
expect(resolvedInfo?.fieldGroup).to.deep.include({
245+
depth: 0,
246+
});
247+
248+
const fieldNode = operation.selectionSet.selections[0];
249+
expect(resolvedInfo?.fieldGroup.fields.get(undefined)).to.deep.equal([
250+
fieldNode,
251+
]);
252+
249253
expect(resolvedInfo?.path).to.deep.include({
250254
prev: undefined,
251255
key: 'result',

src/execution/collectFields.ts

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
12
import { invariant } from '../jsutils/invariant.js';
23
import type { ObjMap } from '../jsutils/ObjMap.js';
34

@@ -34,30 +35,20 @@ import { getDirectiveValues } from './values.js';
3435
*
3536
* The groups's depth is provided so that CollectField algorithm can compute the
3637
* depth of an inline or named fragment with a defer directive.
38+
*
39+
* The group is organized in a map of deferDepth to fields. The deferDepth is
40+
* used to uniquely identify the set of fields within a deferred payload.
3741
*/
3842
export interface FieldGroup {
3943
depth: number;
40-
fields: ReadonlyArray<TaggedFieldNode>;
44+
fields: Map<number | undefined, ReadonlyArray<FieldNode>>;
4145
}
4246

4347
export type GroupedFieldSet = Map<string, FieldGroup>;
4448

45-
interface MutableFieldSet {
49+
interface MutableFieldGroup {
4650
depth: number;
47-
fields: Array<TaggedFieldNode>;
48-
}
49-
50-
/**
51-
* A tagged field node includes the depth of any enclosing defer directive, or
52-
* undefined, if the field is not contained within a deferred fragment.
53-
*
54-
* Because deferred fragments at a given level are merged, the defer depth for a
55-
* field node may be used as a unique id to tag the fields for inclusion within a
56-
* given deferred payload.
57-
*/
58-
export interface TaggedFieldNode {
59-
fieldNode: FieldNode;
60-
deferDepth: number | undefined;
51+
fields: AccumulatorMap<number | undefined, FieldNode>;
6152
}
6253

6354
/**
@@ -79,7 +70,7 @@ export function collectFields(
7970
groupedFieldSet: GroupedFieldSet;
8071
newDeferDepth: number | undefined;
8172
} {
82-
const groupedFieldSet = new Map<string, MutableFieldSet>();
73+
const groupedFieldSet = new Map<string, MutableFieldGroup>();
8374
const newDeferDepth = collectFieldsImpl(
8475
schema,
8576
fragments,
@@ -120,26 +111,28 @@ export function collectSubfields(
120111
groupedFieldSet: GroupedFieldSet;
121112
newDeferDepth: number | undefined;
122113
} {
123-
const groupedFieldSet = new Map<string, MutableFieldSet>();
114+
const groupedFieldSet = new Map<string, MutableFieldGroup>();
124115
let newDeferDepth: number | undefined;
125116
const visitedFragmentNames = new Set<string>();
126117

127-
for (const field of fieldGroup.fields) {
128-
if (field.fieldNode.selectionSet) {
129-
const nestedNewDeferDepth = collectFieldsImpl(
130-
schema,
131-
fragments,
132-
variableValues,
133-
operation,
134-
returnType,
135-
field.fieldNode.selectionSet,
136-
groupedFieldSet,
137-
visitedFragmentNames,
138-
fieldGroup.depth + 1,
139-
field.deferDepth,
140-
);
141-
if (nestedNewDeferDepth !== undefined) {
142-
newDeferDepth = nestedNewDeferDepth;
118+
for (const [deferDepth, fields] of fieldGroup.fields) {
119+
for (const field of fields) {
120+
if (field.selectionSet) {
121+
const nestedNewDeferDepth = collectFieldsImpl(
122+
schema,
123+
fragments,
124+
variableValues,
125+
operation,
126+
returnType,
127+
field.selectionSet,
128+
groupedFieldSet,
129+
visitedFragmentNames,
130+
fieldGroup.depth + 1,
131+
deferDepth,
132+
);
133+
if (nestedNewDeferDepth !== undefined) {
134+
newDeferDepth = nestedNewDeferDepth;
135+
}
143136
}
144137
}
145138
}
@@ -158,7 +151,7 @@ function collectFieldsImpl(
158151
operation: OperationDefinitionNode,
159152
runtimeType: GraphQLObjectType,
160153
selectionSet: SelectionSetNode,
161-
groupedFieldSet: Map<string, MutableFieldSet>,
154+
groupedFieldSet: Map<string, MutableFieldGroup>,
162155
visitedFragmentNames: Set<string>,
163156
depth: number,
164157
deferDepth: number | undefined,
@@ -171,18 +164,15 @@ function collectFieldsImpl(
171164
continue;
172165
}
173166
const key = getFieldEntryKey(selection);
174-
const fieldSet = groupedFieldSet.get(key);
175-
if (fieldSet) {
176-
fieldSet.fields.push({
177-
fieldNode: selection,
178-
deferDepth,
179-
});
180-
} else {
181-
groupedFieldSet.set(key, {
167+
let fieldGroup = groupedFieldSet.get(key);
168+
if (!fieldGroup) {
169+
fieldGroup = {
182170
depth,
183-
fields: [{ fieldNode: selection, deferDepth }],
184-
});
171+
fields: new AccumulatorMap<number | undefined, FieldNode>(),
172+
};
173+
groupedFieldSet.set(key, fieldGroup);
185174
}
175+
fieldGroup.fields.add(deferDepth, selection);
186176
break;
187177
}
188178
case Kind.INLINE_FRAGMENT: {

src/execution/execute.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,8 @@ function shouldExecute(
660660
fieldGroup: FieldGroup,
661661
deferDepth?: number | undefined,
662662
): boolean {
663-
return fieldGroup.fields.some(
664-
({ deferDepth: fieldDeferDepth }) => fieldDeferDepth === deferDepth,
665-
);
663+
const fieldGroupForDeferDepth = fieldGroup.fields.get(deferDepth);
664+
return fieldGroupForDeferDepth !== undefined;
666665
}
667666

668667
/**
@@ -724,7 +723,7 @@ function executeFields(
724723
}
725724

726725
function toNodes(fieldGroup: FieldGroup): ReadonlyArray<FieldNode> {
727-
return fieldGroup.fields.map(({ fieldNode }) => fieldNode);
726+
return [...fieldGroup.fields.values()].flat();
728727
}
729728
/**
730729
* Implements the "Executing fields" section of the spec
@@ -741,7 +740,10 @@ function executeField(
741740
asyncPayloadRecord?: AsyncPayloadRecord,
742741
): PromiseOrValue<unknown> {
743742
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
744-
const fieldName = fieldGroup.fields[0].fieldNode.name.value;
743+
const firstField = (
744+
fieldGroup.fields.values().next().value as Array<FieldNode>
745+
)[0];
746+
const fieldName = firstField.name.value;
745747
const fieldDef = exeContext.schema.getField(parentType, fieldName);
746748
if (!fieldDef) {
747749
return;
@@ -766,7 +768,7 @@ function executeField(
766768
// TODO: find a way to memoize, in case this field is within a List type.
767769
const args = getArgumentValues(
768770
fieldDef,
769-
fieldGroup.fields[0].fieldNode,
771+
firstField,
770772
exeContext.variableValues,
771773
);
772774

@@ -1043,9 +1045,12 @@ function getStreamValues(
10431045

10441046
// validation only allows equivalent streams on multiple fields, so it is
10451047
// safe to only check the first fieldNode for the stream directive
1048+
const firstField = (
1049+
fieldGroup.fields.values().next().value as Array<FieldNode>
1050+
)[0];
10461051
const stream = getDirectiveValues(
10471052
GraphQLStreamDirective,
1048-
fieldGroup.fields[0].fieldNode,
1053+
firstField,
10491054
exeContext.variableValues,
10501055
);
10511056

@@ -1778,7 +1783,10 @@ function executeSubscription(
17781783
FieldGroup,
17791784
];
17801785
const [responseName, fieldGroup] = firstRootField;
1781-
const fieldName = fieldGroup.fields[0].fieldNode.name.value;
1786+
const firstField = (
1787+
fieldGroup.fields.values().next().value as Array<FieldNode>
1788+
)[0];
1789+
const fieldName = firstField.name.value;
17821790
const fieldDef = schema.getField(rootType, fieldName);
17831791

17841792
if (!fieldDef) {
@@ -1803,11 +1811,7 @@ function executeSubscription(
18031811

18041812
// Build a JS object of arguments from the field.arguments AST, using the
18051813
// variables scope to fulfill any variable references.
1806-
const args = getArgumentValues(
1807-
fieldDef,
1808-
fieldGroup.fields[0].fieldNode,
1809-
variableValues,
1810-
);
1814+
const args = getArgumentValues(fieldDef, firstField, variableValues);
18111815

18121816
// The resolve function's optional third argument is a context value that
18131817
// is provided to every resolve function within an execution. It is commonly

src/type/definition.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -892,10 +892,7 @@ export interface GraphQLResolveInfo {
892892
readonly fieldName: string;
893893
readonly fieldGroup: {
894894
depth: number;
895-
fields: ReadonlyArray<{
896-
fieldNode: FieldNode;
897-
deferDepth: number | undefined;
898-
}>;
895+
fields: Map<number | undefined, ReadonlyArray<FieldNode>>;
899896
};
900897
readonly deferDepth: number | undefined;
901898
readonly returnType: GraphQLOutputType;

src/validation/rules/SingleFieldSubscriptionsRule.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ export function SingleFieldSubscriptionsRule(
5252
const fieldGroups = [...groupedFieldSet.values()];
5353
const extraFieldGroups = fieldGroups.slice(1);
5454
const extraFields = extraFieldGroups
55-
.map(({ fields }) => fields)
56-
.flat()
57-
.map(({ fieldNode }) => fieldNode);
55+
.map(({ fields }) => [...fields.values()].flat())
56+
.flat();
5857
context.reportError(
5958
new GraphQLError(
6059
operationName != null
@@ -65,9 +64,7 @@ export function SingleFieldSubscriptionsRule(
6564
);
6665
}
6766
for (const fieldGroup of groupedFieldSet.values()) {
68-
const fieldNodes = fieldGroup.fields.map(
69-
({ fieldNode }) => fieldNode,
70-
);
67+
const fieldNodes = [...fieldGroup.fields.values()].flat();
7168
const fieldName = fieldNodes[0].name.value;
7269
if (fieldName.startsWith('__')) {
7370
context.reportError(

0 commit comments

Comments
 (0)