From 97b91e019eb1aa04e4eab209c5b707739dc46d93 Mon Sep 17 00:00:00 2001 From: Alexandre Carlton Date: Sat, 11 Oct 2025 21:43:55 +1100 Subject: [PATCH] Memoize ResultPath level In our new `PerLevelDataLoaderDispatchStrategy`, we incorporate the level of the current field (through the `ResultPath`) to detemrine when we should dispatch `DataLoader`s. For: - a deep enough level - a large number of fields this can become quite taxing. To optimise this, we memoize the level by calculating it in the constructor, which should have a negligible cost. --- .../java/graphql/execution/ResultPath.java | 14 +++++--------- .../graphql/execution/ResultPathTest.groovy | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/graphql/execution/ResultPath.java b/src/main/java/graphql/execution/ResultPath.java index 55d4984f2b..3c12b559e8 100644 --- a/src/main/java/graphql/execution/ResultPath.java +++ b/src/main/java/graphql/execution/ResultPath.java @@ -39,23 +39,27 @@ public static ResultPath rootPath() { // hash is effective immutable but lazily initialized similar to the hash code of java.lang.String private int hash; private final String toStringValue; + private final int level; private ResultPath() { parent = null; segment = null; this.toStringValue = initString(); + this.level = 0; } private ResultPath(ResultPath parent, String segment) { this.parent = assertNotNull(parent, () -> "Must provide a parent path"); this.segment = assertNotNull(segment, () -> "Must provide a sub path"); this.toStringValue = initString(); + this.level = parent.level + 1; } private ResultPath(ResultPath parent, int segment) { this.parent = assertNotNull(parent, () -> "Must provide a parent path"); this.segment = segment; this.toStringValue = initString(); + this.level = parent.level; } private String initString() { @@ -71,15 +75,7 @@ private String initString() { } public int getLevel() { - int counter = 0; - ResultPath currentPath = this; - while (currentPath != null) { - if (currentPath.segment instanceof String) { - counter++; - } - currentPath = currentPath.parent; - } - return counter; + return level; } public ResultPath getPathWithoutListEnd() { diff --git a/src/test/groovy/graphql/execution/ResultPathTest.groovy b/src/test/groovy/graphql/execution/ResultPathTest.groovy index a7fe960f19..146970cd6e 100644 --- a/src/test/groovy/graphql/execution/ResultPathTest.groovy +++ b/src/test/groovy/graphql/execution/ResultPathTest.groovy @@ -32,7 +32,7 @@ class ResultPathTest extends Specification { actual.toString() == expected where: - actual || expected + actual || expected ResultPath.rootPath() || "" ResultPath.rootPath().segment("A") || "/A" ResultPath.rootPath().segment("A").segment(1).segment("B") || "/A[1]/B" @@ -46,12 +46,27 @@ class ResultPathTest extends Specification { actual.toList() == expected where: - actual || expected + actual || expected ResultPath.rootPath() || [] ResultPath.rootPath().segment("A").sibling("B") || ["B"] ResultPath.rootPath().segment("A").segment(1).segment("B").sibling("C") || ["A", 1, "C"] } + @Unroll + "unit test getLevel works as expected : #actual"() { + + expect: + actual.getLevel() == expected + + where: + actual || expected + ResultPath.rootPath() || 0 + ResultPath.rootPath().segment("A") || 1 + ResultPath.rootPath().segment("A").segment("B") || 2 + ResultPath.rootPath().segment("A").segment(1).segment("B") || 2 + ResultPath.rootPath().segment("A").segment("B").segment(1) || 2 + } + def "full integration test of path support"() { when: