Skip to content

Commit c42baa7

Browse files
committed
Made the cache lookup be either async or not
1 parent 1e3785a commit c42baa7

File tree

3 files changed

+23
-11
lines changed

3 files changed

+23
-11
lines changed

src/main/java/graphql/execution/preparsed/caching/CachingDocumentProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.concurrent.CompletableFuture;
1111
import java.util.function.Function;
1212

13+
import static graphql.execution.Async.toCompletableFuture;
14+
1315
/**
1416
* The CachingDocumentProvider allows previously parsed and validated operations to be cached and
1517
* hence re-used. This can lead to significant time savings, especially for large operations.
@@ -56,11 +58,11 @@ public DocumentCache getDocumentCache() {
5658
public CompletableFuture<PreparsedDocumentEntry> getDocumentAsync(ExecutionInput executionInput, Function<ExecutionInput, PreparsedDocumentEntry> parseAndValidateFunction) {
5759
if (documentCache.isNoop()) {
5860
// saves creating keys and doing a lookup that will just call this function anyway
59-
return CompletableFuture.completedFuture(parseAndValidateFunction.apply(executionInput));
61+
return toCompletableFuture(parseAndValidateFunction.apply(executionInput));
6062
}
6163
DocumentCache.DocumentCacheKey cacheKey = new DocumentCache.DocumentCacheKey(executionInput.getQuery(), executionInput.getOperationName(), executionInput.getLocale());
62-
PreparsedDocumentEntry cacheEntry = documentCache.get(cacheKey, key -> parseAndValidateFunction.apply(executionInput));
63-
return CompletableFuture.completedFuture(cacheEntry);
64+
Object cacheEntry = documentCache.get(cacheKey, key -> parseAndValidateFunction.apply(executionInput));
65+
return toCompletableFuture(cacheEntry);
6466
}
6567

6668
}

src/main/java/graphql/execution/preparsed/caching/DocumentCache.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.execution.preparsed.caching;
22

3+
import graphql.DuckTyped;
34
import graphql.PublicApi;
45
import graphql.execution.preparsed.PreparsedDocumentEntry;
56
import org.jspecify.annotations.NullMarked;
@@ -17,14 +18,18 @@
1718
@NullMarked
1819
public interface DocumentCache {
1920
/**
20-
* Called to get a document that has previously been parsed ad validated.
21+
* Called to get a document that has previously been parsed ad validated. The return value of this method
22+
* can be either a {@link PreparsedDocumentEntry} or a {@link java.util.concurrent.CompletableFuture} promise
23+
* to a {@link PreparsedDocumentEntry}. This allows caches that are in memory to return direct values OR
24+
* if the cache is distributed, it can return a promise to a value.
2125
*
2226
* @param key the cache key
2327
* @param mappingFunction if the value is missing in cache this function can be called to create a value
2428
*
25-
* @return a non null document entry
29+
* @return a non-null {@link PreparsedDocumentEntry} or a promise to one via a {@link java.util.concurrent.CompletableFuture}
2630
*/
27-
PreparsedDocumentEntry get(DocumentCacheKey key, Function<DocumentCacheKey, PreparsedDocumentEntry> mappingFunction);
31+
@DuckTyped(shape = "PreparsedDocumentEntry | CompletableFuture<PreparsedDocumentEntry")
32+
Object get(DocumentCacheKey key, Function<DocumentCacheKey, PreparsedDocumentEntry> mappingFunction);
2833

2934
/**
3035
* @return true if the cache in fact does no caching otherwise false. This helps the implementation optimise how the cache is used or not.
@@ -35,6 +40,7 @@ public interface DocumentCache {
3540
* Called to clear the cache. If your implementation doesn't support this, then just no op the method
3641
*/
3742
void invalidateAll();
43+
3844
/**
3945
* This represents the key to the document cache
4046
*/

src/test/groovy/graphql/execution/preparsed/caching/CachingDocumentProviderTest.groovy

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package graphql.execution.preparsed.caching
22

3-
43
import com.github.benmanes.caffeine.cache.Caffeine
54
import com.github.benmanes.caffeine.cache.Ticker
65
import graphql.ExecutionInput
@@ -12,9 +11,11 @@ import graphql.parser.Parser
1211
import spock.lang.Specification
1312

1413
import java.time.Duration
14+
import java.util.concurrent.CompletableFuture
1515
import java.util.function.Function
1616

1717
import static graphql.ExecutionInput.newExecutionInput
18+
import static graphql.execution.preparsed.caching.DocumentCache.DocumentCacheKey
1819

1920
class CachingDocumentProviderTest extends Specification {
2021
private String heroQuery1
@@ -160,11 +161,14 @@ class CachingDocumentProviderTest extends Specification {
160161

161162
def cache = new DocumentCache() {
162163
// not really useful in production since its unbounded
163-
def map = new HashMap<DocumentCache.DocumentCacheKey,PreparsedDocumentEntry>()
164+
def map = new HashMap<DocumentCacheKey, PreparsedDocumentEntry>()
164165

165166
@Override
166-
PreparsedDocumentEntry get(DocumentCache.DocumentCacheKey key, Function<DocumentCache.DocumentCacheKey, PreparsedDocumentEntry> mappingFunction) {
167-
return map.computeIfAbsent(key,mappingFunction)
167+
Object get(DocumentCacheKey key, Function<DocumentCacheKey, PreparsedDocumentEntry> mappingFunction) {
168+
// we can have async values
169+
return CompletableFuture.supplyAsync {
170+
return map.computeIfAbsent(key, mappingFunction)
171+
}
168172
}
169173

170174
@Override
@@ -289,7 +293,7 @@ class CachingDocumentProviderTest extends Specification {
289293
def caffeineCache = Caffeine.newBuilder()
290294
.ticker(ticker)
291295
.expireAfterAccess(Duration.ofMinutes(2))
292-
.<DocumentCache.DocumentCacheKey, PreparsedDocumentEntry> build()
296+
.<DocumentCacheKey, PreparsedDocumentEntry> build()
293297
def documentCache = new CaffeineDocumentCache(caffeineCache)
294298

295299
// note this is a custom caffeine instance pass in

0 commit comments

Comments
 (0)