Skip to content

Commit f9d67dc

Browse files
committed
Pass depth through execution call chain instead of recomputing
Addresses review feedback: rather than walking the Path linked list at every executeField invocation, thread the current field depth as an explicit parameter through executeFields/executeField/completeValue and its helpers. completeObjectValue increments depth when descending into sub-selections; list items and abstract-type resolution keep the same depth as their parent field. This changes the depth check from O(depth) per field resolution to O(1), while preserving identical semantics (list indices still do not count toward depth).
1 parent 43e0e57 commit f9d67dc

File tree

1 file changed

+60
-28
lines changed

1 file changed

+60
-28
lines changed

src/execution/execute.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -445,19 +445,34 @@ function executeOperation(
445445

446446
switch (operation.operation) {
447447
case OperationTypeNode.QUERY:
448-
return executeFields(exeContext, rootType, rootValue, path, rootFields);
448+
return executeFields(
449+
exeContext,
450+
rootType,
451+
rootValue,
452+
path,
453+
rootFields,
454+
1,
455+
);
449456
case OperationTypeNode.MUTATION:
450457
return executeFieldsSerially(
451458
exeContext,
452459
rootType,
453460
rootValue,
454461
path,
455462
rootFields,
463+
1,
456464
);
457465
case OperationTypeNode.SUBSCRIPTION:
458466
// TODO: deprecate `subscribe` and move all logic here
459467
// Temporary solution until we finish merging execute and subscribe together
460-
return executeFields(exeContext, rootType, rootValue, path, rootFields);
468+
return executeFields(
469+
exeContext,
470+
rootType,
471+
rootValue,
472+
path,
473+
rootFields,
474+
1,
475+
);
461476
}
462477
}
463478

@@ -471,6 +486,7 @@ function executeFieldsSerially(
471486
sourceValue: unknown,
472487
path: Path | undefined,
473488
fields: Map<string, ReadonlyArray<FieldNode>>,
489+
depth: number,
474490
): PromiseOrValue<ObjMap<unknown>> {
475491
return promiseReduce(
476492
fields.entries(),
@@ -482,6 +498,7 @@ function executeFieldsSerially(
482498
sourceValue,
483499
fieldNodes,
484500
fieldPath,
501+
depth,
485502
);
486503
if (result === undefined) {
487504
return results;
@@ -509,6 +526,7 @@ function executeFields(
509526
sourceValue: unknown,
510527
path: Path | undefined,
511528
fields: Map<string, ReadonlyArray<FieldNode>>,
529+
depth: number,
512530
): PromiseOrValue<ObjMap<unknown>> {
513531
const results = Object.create(null);
514532
let containsPromise = false;
@@ -522,6 +540,7 @@ function executeFields(
522540
sourceValue,
523541
fieldNodes,
524542
fieldPath,
543+
depth,
525544
);
526545

527546
if (result !== undefined) {
@@ -578,22 +597,6 @@ function checkAliasCount(
578597
}
579598
}
580599

581-
/**
582-
* Counts the field depth represented by a Path. Only string keys are counted
583-
* (numeric keys represent list indices and should not increase the depth).
584-
*/
585-
function pathDepth(path: Path | undefined): number {
586-
let depth = 0;
587-
let current = path;
588-
while (current !== undefined) {
589-
if (typeof current.key === 'string') {
590-
depth++;
591-
}
592-
current = current.prev;
593-
}
594-
return depth;
595-
}
596-
597600
/**
598601
* Implements the "Executing fields" section of the spec
599602
* In particular, this function figures out the value that the field returns by
@@ -606,16 +609,14 @@ function executeField(
606609
source: unknown,
607610
fieldNodes: ReadonlyArray<FieldNode>,
608611
path: Path,
612+
depth: number,
609613
): PromiseOrValue<unknown> {
610614
// Check depth limit before resolving the field.
611-
if (exeContext.maxDepth !== undefined) {
612-
const depth = pathDepth(path);
613-
if (depth > exeContext.maxDepth) {
614-
throw new GraphQLError(
615-
`Query depth limit of ${exeContext.maxDepth} exceeded, found depth: ${depth}.`,
616-
{ nodes: fieldNodes },
617-
);
618-
}
615+
if (exeContext.maxDepth !== undefined && depth > exeContext.maxDepth) {
616+
throw new GraphQLError(
617+
`Query depth limit of ${exeContext.maxDepth} exceeded, found depth: ${depth}.`,
618+
{ nodes: fieldNodes },
619+
);
619620
}
620621

621622
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]);
@@ -655,7 +656,15 @@ function executeField(
655656
let completed;
656657
if (isPromise(result)) {
657658
completed = result.then((resolved) =>
658-
completeValue(exeContext, returnType, fieldNodes, info, path, resolved),
659+
completeValue(
660+
exeContext,
661+
returnType,
662+
fieldNodes,
663+
info,
664+
path,
665+
resolved,
666+
depth,
667+
),
659668
);
660669
} else {
661670
completed = completeValue(
@@ -665,6 +674,7 @@ function executeField(
665674
info,
666675
path,
667676
result,
677+
depth,
668678
);
669679
}
670680

@@ -755,6 +765,7 @@ function completeValue(
755765
info: GraphQLResolveInfo,
756766
path: Path,
757767
result: unknown,
768+
depth: number,
758769
): PromiseOrValue<unknown> {
759770
// If result is an Error, throw a located error.
760771
if (result instanceof Error) {
@@ -771,6 +782,7 @@ function completeValue(
771782
info,
772783
path,
773784
result,
785+
depth,
774786
);
775787
if (completed === null) {
776788
throw new Error(
@@ -794,6 +806,7 @@ function completeValue(
794806
info,
795807
path,
796808
result,
809+
depth,
797810
);
798811
}
799812

@@ -813,6 +826,7 @@ function completeValue(
813826
info,
814827
path,
815828
result,
829+
depth,
816830
);
817831
}
818832

@@ -825,6 +839,7 @@ function completeValue(
825839
info,
826840
path,
827841
result,
842+
depth,
828843
);
829844
}
830845
/* c8 ignore next 6 */
@@ -846,6 +861,7 @@ function completeListValue(
846861
info: GraphQLResolveInfo,
847862
path: Path,
848863
result: unknown,
864+
depth: number,
849865
): PromiseOrValue<ReadonlyArray<unknown>> {
850866
if (!isIterableObject(result)) {
851867
throw new GraphQLError(
@@ -872,6 +888,7 @@ function completeListValue(
872888
info,
873889
itemPath,
874890
resolved,
891+
depth,
875892
),
876893
);
877894
} else {
@@ -882,6 +899,7 @@ function completeListValue(
882899
info,
883900
itemPath,
884901
item,
902+
depth,
885903
);
886904
}
887905

@@ -937,6 +955,7 @@ function completeAbstractValue(
937955
info: GraphQLResolveInfo,
938956
path: Path,
939957
result: unknown,
958+
depth: number,
940959
): PromiseOrValue<ObjMap<unknown>> {
941960
const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver;
942961
const contextValue = exeContext.contextValue;
@@ -958,6 +977,7 @@ function completeAbstractValue(
958977
info,
959978
path,
960979
result,
980+
depth,
961981
),
962982
);
963983
}
@@ -976,6 +996,7 @@ function completeAbstractValue(
976996
info,
977997
path,
978998
result,
999+
depth,
9791000
);
9801001
}
9811002

@@ -1044,12 +1065,15 @@ function completeObjectValue(
10441065
info: GraphQLResolveInfo,
10451066
path: Path,
10461067
result: unknown,
1068+
depth: number,
10471069
): PromiseOrValue<ObjMap<unknown>> {
10481070
// Collect sub-fields to execute to complete this value.
10491071
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
10501072

10511073
checkAliasCount(exeContext, subFieldNodes);
10521074

1075+
const subDepth = depth + 1;
1076+
10531077
// If there is an isTypeOf predicate function, call it with the
10541078
// current result. If isTypeOf returns false, then raise an error rather
10551079
// than continuing execution.
@@ -1067,6 +1091,7 @@ function completeObjectValue(
10671091
result,
10681092
path,
10691093
subFieldNodes,
1094+
subDepth,
10701095
);
10711096
});
10721097
}
@@ -1076,7 +1101,14 @@ function completeObjectValue(
10761101
}
10771102
}
10781103

1079-
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1104+
return executeFields(
1105+
exeContext,
1106+
returnType,
1107+
result,
1108+
path,
1109+
subFieldNodes,
1110+
subDepth,
1111+
);
10801112
}
10811113

10821114
function invalidReturnTypeError(

0 commit comments

Comments
 (0)