Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

feature: Expose API to let client define number of expected values in a Get. #6655

Merged
merged 46 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6c2aa5f
Expose API to let client define number of expected values
LucasIME Jul 4, 2023
99f2125
Add generated changelog entries
svc-changelog Jul 4, 2023
7e76c60
accept revapi change
LucasIME Jul 4, 2023
e3224bd
T -> Timestamp
LucasIME Jul 6, 2023
8d5320d
Extract validation to private method and unsafe log the cells
LucasIME Jul 6, 2023
d3afa8d
fix documentation typo
LucasIME Jul 6, 2023
37ac526
Add cache test
LucasIME Jul 20, 2023
b557bc7
fix test
LucasIME Jul 20, 2023
90477bd
test for not reducing
LucasIME Jul 20, 2023
ffaa25c
description
LucasIME Jul 20, 2023
648e54e
Add methods with cachedLookupRef to TransactionScopedCache gets
LucasIME Jul 20, 2023
f273357
Add test verifying we don't decrease number of expected cells if cach…
LucasIME Jul 20, 2023
773e1ad
verify no lock is ever called
LucasIME Jul 20, 2023
34c0b25
Merge branch 'develop' into lmeireles/expose-get-with-expected-size
LucasIME Jul 20, 2023
0a13148
rev api
LucasIME Jul 20, 2023
d54908e
catch interrupted properly
LucasIME Jul 24, 2023
3d913ff
skip lock check and not lock
LucasIME Jul 24, 2023
797e635
fix test description
LucasIME Jul 24, 2023
2926f79
Array equals instead of !=
LucasIME Jul 24, 2023
e8c08c7
testing all fetched cached
LucasIME Jul 24, 2023
f48fd14
Extract validor to separate class
LucasIME Jul 25, 2023
89cb066
update expected exception class
LucasIME Jul 25, 2023
bf5b271
Add test to verify we throw if we have cached more values than we expect
LucasIME Jul 25, 2023
2739a48
Restrict dangerous API usage
LucasIME Jul 25, 2023
918ead8
rename validator
LucasIME Jul 25, 2023
6a1426f
Treat interrupted and execution exceptions differently.
LucasIME Aug 1, 2023
2ab80f9
Make MoreCellsPresentThanExpectedException SafeLoggable
LucasIME Aug 1, 2023
a42100a
assert args in exception
LucasIME Aug 1, 2023
f91d0ba
cell count validator tests
LucasIME Aug 1, 2023
8f0a05d
add test to verify we don't filter down the cells we pass to the cell…
LucasIME Aug 1, 2023
e147c14
import
LucasIME Aug 1, 2023
9d61cc7
spotless
LucasIME Aug 1, 2023
b2292ce
fix test style
LucasIME Aug 2, 2023
82da62a
Changing getWithExpectedSize to return a Result type (#6686)
LucasIME Aug 4, 2023
ad4d984
Merge branch 'develop' into lmeireles/expose-get-with-expected-size
LucasIME Aug 7, 2023
3227916
rev api
LucasIME Aug 7, 2023
806abe0
Autorelease 0.908.0-rc1
LucasIME Aug 7, 2023
b8d981f
store number of expected cells on exception too
LucasIME Aug 9, 2023
9e25ffa
Autorelease 0.908.0-rc2
LucasIME Aug 9, 2023
11951f1
Merge branch 'develop' into lmeireles/expose-get-with-expected-size
LucasIME Aug 22, 2023
f1912e1
fix ambiguous reference
LucasIME Aug 22, 2023
f11d972
don't count deleted local writes as expected cells
LucasIME Aug 23, 2023
1f68638
Merge branch 'develop' into lmeireles/expose-get-with-expected-size
LucasIME Aug 23, 2023
cad58d2
Autorelease 0.919.0-rc1
LucasIME Aug 23, 2023
b3f7284
Autorelease 0.919.0-rc2
LucasIME Aug 23, 2023
86831da
missing space
LucasIME Aug 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .palantir/revapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,28 @@ acceptedBreaks:
new: "parameter <T> long com.palantir.atlasdb.keyvalue.cassandra.thrift.ThriftObjectSizeUtils::getCollectionSize(java.util.Collection<T>,\
\ ===java.util.function.ToLongFunction<T>===)"
justification: "Avoid autoboxing long to Long"
"0.884.0":
com.palantir.atlasdb:atlasdb-api:
- code: "java.method.addedToInterface"
new: "method java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell, byte[]> com.palantir.atlasdb.transaction.api.Transaction::getWithExpectedNumberOfCells(com.palantir.atlasdb.keyvalue.api.TableReference,\
\ java.util.Set<com.palantir.atlasdb.keyvalue.api.Cell>, int)"
justification: "Just introducing new Get API"
"0.885.0":
com.palantir.atlasdb:atlasdb-api:
- code: "java.method.addedToInterface"
new: "method com.google.common.util.concurrent.ListenableFuture<java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell,\
\ byte[]>> com.palantir.atlasdb.keyvalue.api.cache.TransactionScopedCache::getAsyncWithCachedRef(com.palantir.atlasdb.keyvalue.api.TableReference,\
\ java.util.Set<com.palantir.atlasdb.keyvalue.api.Cell>, java.util.function.Function<com.palantir.atlasdb.keyvalue.api.cache.TransactionScopedCache.CacheLookupResult,\
\ com.google.common.util.concurrent.ListenableFuture<java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell,\
\ byte[]>>>)"
justification: "just adding a new get method"
- code: "java.method.addedToInterface"
new: "method java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell, byte[]> com.palantir.atlasdb.keyvalue.api.cache.TransactionScopedCache::getWithCachedRef(com.palantir.atlasdb.keyvalue.api.TableReference,\
\ java.util.Set<com.palantir.atlasdb.keyvalue.api.Cell>, java.util.function.Function<com.palantir.atlasdb.keyvalue.api.cache.TransactionScopedCache.CacheLookupResult,\
\ com.google.common.util.concurrent.ListenableFuture<java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell,\
\ byte[]>>>)"
justification: "just adding a new get method"
- code: "java.method.addedToInterface"
new: "method java.util.Map<com.palantir.atlasdb.keyvalue.api.Cell, byte[]> com.palantir.atlasdb.transaction.api.Transaction::getWithExpectedNumberOfCells(com.palantir.atlasdb.keyvalue.api.TableReference,\
\ java.util.Set<com.palantir.atlasdb.keyvalue.api.Cell>, long)"
justification: "just adding a new get method"
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ public Map<Cell, byte[]> get(
return AtlasFutures.getUnchecked(getAsync(tableReference, cells, valueLoader));
}

@Override
public Map<Cell, byte[]> getWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return AtlasFutures.getUnchecked(getAsyncWithCachedRef(tableReference, cells, valueLoader));
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsync(
TableReference tableReference,
Expand All @@ -59,6 +67,14 @@ public ListenableFuture<Map<Cell, byte[]>> getAsync(
return valueLoader.apply(cells);
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsyncWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return valueLoader.apply(CacheLookupResult.of(Map.of(), cells));
}

@Override
public NavigableMap<byte[], RowResult<byte[]>> getRows(
TableReference tableRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public Map<Cell, byte[]> get(
return delegate.get(tableReference, cells, valueLoader);
}

@Override
public Map<Cell, byte[]> getWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return delegate.getWithCachedRef(tableReference, cells, valueLoader);
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsync(
TableReference tableReference,
Expand All @@ -64,6 +72,14 @@ public ListenableFuture<Map<Cell, byte[]>> getAsync(
return delegate.getAsync(tableReference, cells, valueLoader);
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsyncWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return delegate.getAsyncWithCachedRef(tableReference, cells, valueLoader);
}

@Override
public NavigableMap<byte[], RowResult<byte[]>> getRows(
TableReference tableRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.NavigableMap;
import java.util.Set;
import java.util.function.Function;
import org.immutables.value.Value;

/**
* The {@link LockWatchValueScopingCache} will provide one of these to every (relevant) transaction, and this will
Expand Down Expand Up @@ -60,11 +61,21 @@ Map<Cell, byte[]> get(
Set<Cell> cells,
Function<Set<Cell>, ListenableFuture<Map<Cell, byte[]>>> valueLoader);

Map<Cell, byte[]> getWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader);
Comment on lines +64 to +67
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have also just replaced the existing get and getAsync to receive the CacheLookupResult instead of creating new two separate methods. Let me know what you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How bad would this change have been? Basically I think it's nicer to just have one get/getAsync, but I understand refactoring this might be very difficult/costly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very, tbh. Although this PR is already getting quite big, so I'll try to do the refactor in a separate one.


ListenableFuture<Map<Cell, byte[]>> getAsync(
TableReference tableReference,
Set<Cell> cells,
Function<Set<Cell>, ListenableFuture<Map<Cell, byte[]>>> valueLoader);

ListenableFuture<Map<Cell, byte[]>> getAsyncWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader);

/**
* The cache will try to fulfil as much of the request as possible with cached values. In the case where some of the
* columns are present in the cache for a row, the cellLoader will be used to read the remaining cells remotely.
Expand Down Expand Up @@ -93,4 +104,18 @@ NavigableMap<byte[], RowResult<byte[]>> getRows(
HitDigest getHitDigest();

TransactionScopedCache createReadOnlyCache(CommitUpdate commitUpdate);

@Value.Immutable
interface CacheLookupResult {
Map<Cell, CacheValue> cacheHits();

Set<Cell> missedCells();

static CacheLookupResult of(Map<Cell, CacheValue> cachedValues, Set<Cell> missedCells) {
return ImmutableCacheLookupResult.builder()
.cacheHits(cachedValues)
.missedCells(missedCells)
.build();
}
}
LucasIME marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,31 @@ Iterator<Map.Entry<Cell, byte[]>> getSortedColumns(
@Idempotent
Map<Cell, byte[]> get(TableReference tableRef, Set<Cell> cells);

/**
* Similar to {@link #get(TableReference, Set)}, but allows the caller to specify the number of expected present
* cells. This is important to allow clients that might have schemas that guarantees only a subset of the columns
* are present to still benefit from being able to skip the immutable timestamp lock check on non-empty reads of
* thoroughly swept tables.
*
* If we find values for the exact number of expected present cells, we'll skip the immutable timestamp lock check.
* If we find less values, we'll perform the immutable timestamp lock check.
* If we find more values, we'll throw an exception, as it means that either:
* 1 - There is a bug in the implementation of this method.
* 2 - The client is making an incorrect assumption about the maximum number of values that will be present, and
* we could have returned empty values in the past when we should have not.
*
* WARNING: This method should only be used if you REALLY know what you're doing. Otherwise, you could have
* correctness issues by reading empty values when one is actually present if you don't use this method correctly.
LucasIME marked this conversation as resolved.
Show resolved Hide resolved
*
* @param tableRef the table from which to get the values
* @param cells the cells for which we want to get the values
* @param expectedNumberOfPresentCells the maximum number of cells that are expected to be present.
* @return a {@link Map} from {@link Cell} to {@code byte[]} representing cell/value pairs
*/
LucasIME marked this conversation as resolved.
Show resolved Hide resolved
@Idempotent
Map<Cell, byte[]> getWithExpectedNumberOfCells(
TableReference tableRef, Set<Cell> cells, long expectedNumberOfPresentCells);

/**
* Gets the values associated for each cell in {@code cells} from table specified by {@code tableRef}. It is not
* guaranteed that the actual implementations are in fact asynchronous.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,28 @@ public Map<Cell, byte[]> get(TableReference tableRef, Set<Cell> cells) {
return getWithLoader(
tableRef,
cells,
(tableReference, toRead) -> Futures.immediateFuture(super.get(tableReference, toRead)))
(tableReference, _cachedCells, toRead) ->
Futures.immediateFuture(super.get(tableReference, toRead)))
.get();
} catch (InterruptedException | ExecutionException e) {
throw Throwables.rewrapAndThrowUncheckedException(e.getCause());
LucasIME marked this conversation as resolved.
Show resolved Hide resolved
}
}

@Override
public Map<Cell, byte[]> getWithExpectedNumberOfCells(
TableReference tableRef, Set<Cell> cells, long expectedNumberOfPresentCells) {
try {
return getWithLoader(tableRef, cells, (tableReference, cachedCells, toRead) -> {
long nonEmptyValuesInCache = cachedCells.values().stream()
.filter(value -> value != PtBytes.EMPTY_BYTE_ARRAY)
.count();
long numberOfCellsExpectingValuePostCache =
expectedNumberOfPresentCells - nonEmptyValuesInCache;

return Futures.immediateFuture(super.getWithExpectedNumberOfCells(
tableReference, toRead, numberOfCellsExpectingValuePostCache));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to add some tests to this class verifying that we call super... with less expected values if we have things cached, but couldn't think of a good way to do it... any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. It is tangential, but if we pass in a mock delegate to the constructor we can verify stuff on it right? Admittedly this kind of tests ForwardingTransaction itself too but I think that's fine

})
.get();
} catch (InterruptedException | ExecutionException e) {
throw Throwables.rewrapAndThrowUncheckedException(e.getCause());
Expand All @@ -133,7 +154,7 @@ public Map<Cell, byte[]> get(TableReference tableRef, Set<Cell> cells) {

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsync(TableReference tableRef, Set<Cell> cells) {
return getWithLoader(tableRef, cells, super::getAsync);
return getWithLoader(tableRef, cells, (table, _cacheCells, cellsToLoad) -> super.getAsync(table, cellsToLoad));
}

private ListenableFuture<Map<Cell, byte[]>> getWithLoader(
Expand All @@ -156,7 +177,7 @@ private ListenableFuture<Map<Cell, byte[]>> getWithLoader(
}

return Futures.transform(
cellLoader.load(tableRef, toLoad),
cellLoader.load(tableRef, cacheHit, toLoad),
loadedCells -> {
cacheLoadedCells(tableRef, toLoad, loadedCells);
cacheHit.putAll(loadedCells);
Expand Down Expand Up @@ -271,6 +292,7 @@ public void abort() {

@FunctionalInterface
private interface CellLoader {
ListenableFuture<Map<Cell, byte[]>> load(TableReference tableReference, Set<Cell> toRead);
ListenableFuture<Map<Cell, byte[]>> load(
TableReference tableReference, Map<Cell, byte[]> cachedCells, Set<Cell> toRead);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public Map<Cell, byte[]> get(TableReference tableRef, Set<Cell> cells) {
return delegate().get(tableRef, cells);
}

@Override
public Map<Cell, byte[]> getWithExpectedNumberOfCells(
TableReference tableRef, Set<Cell> cells, long expectedNumberOfPresentCells) {
return delegate().getWithExpectedNumberOfCells(tableRef, cells, expectedNumberOfPresentCells);
}

@Override
public BatchingVisitable<RowResult<byte[]>> getRange(TableReference tableRef, RangeRequest rangeRequest) {
return delegate().getRange(tableRef, rangeRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ public Map<Cell, byte[]> get(TableReference tableRef, Set<Cell> cells) {
return delegate().get(tableRef, cells);
}

@Override
public Map<Cell, byte[]> getWithExpectedNumberOfCells(
TableReference tableRef, Set<Cell> cells, long expectedNumberOfPresentCells) {
checkTableName(tableRef);
return delegate.getWithExpectedNumberOfCells(tableRef, cells, expectedNumberOfPresentCells);
}

@Override
public BatchingVisitable<RowResult<byte[]>> getRange(TableReference tableRef, RangeRequest rangeRequest) {
checkTableName(tableRef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;
import org.immutables.value.Value;

@ThreadSafe
final class TransactionScopedCacheImpl implements TransactionScopedCache {
public final class TransactionScopedCacheImpl implements TransactionScopedCache {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we're now making this public? Makes it API breaks for this class much easier in the future keeping it package-level.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see, in SnapshotTransactionTest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, also felt a bit uneasy when doing the change to allow easier instantiation from the tests. But felt better than mocking or re-implementing a test version of it.

private final TransactionCacheValueStore valueStore;
private final CacheMetrics metrics;
private volatile boolean finalised = false;
Expand All @@ -56,7 +55,7 @@ private TransactionScopedCacheImpl(TransactionCacheValueStore valueStore, CacheM
this.metrics = metrics;
}

static TransactionScopedCache create(ValueCacheSnapshot snapshot, CacheMetrics metrics) {
public static TransactionScopedCache create(ValueCacheSnapshot snapshot, CacheMetrics metrics) {
return new TransactionScopedCacheImpl(new TransactionCacheValueStoreImpl(snapshot), metrics);
}

Expand All @@ -81,15 +80,34 @@ public Map<Cell, byte[]> get(
return AtlasFutures.getUnchecked(getAsync(tableReference, cells, valueLoader));
}

@Override
public Map<Cell, byte[]> getWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
ensureNotFinalised();
return AtlasFutures.getUnchecked(getAsyncWithCachedRef(tableReference, cells, valueLoader));
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsync(
TableReference tableReference,
Set<Cell> cells,
Function<Set<Cell>, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
ensureNotFinalised();
return getAsyncWithCachedRef(
tableReference, cells, cacheLookupResult -> valueLoader.apply(cacheLookupResult.missedCells()));
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsyncWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
ensureNotFinalised();
// Short-cut all the logic below if the table is not watched.
if (!valueStore.isWatched(tableReference)) {
return valueLoader.apply(cells);
return valueLoader.apply(CacheLookupResult.of(Map.of(), cells));
}

CacheLookupResult cacheLookup = cacheLookup(tableReference, cells);
Expand All @@ -98,7 +116,7 @@ public ListenableFuture<Map<Cell, byte[]>> getAsync(
return Futures.immediateFuture(filterEmptyValues(cacheLookup.cacheHits()));
} else {
return Futures.transform(
valueLoader.apply(cacheLookup.missedCells()),
valueLoader.apply(cacheLookup),
uncachedValues -> processUncachedCells(
tableReference, cacheLookup.cacheHits(), cacheLookup.missedCells(), uncachedValues),
MoreExecutors.directExecutor());
Expand Down Expand Up @@ -246,18 +264,4 @@ private static Map<Cell, byte[]> filterEmptyValues(Map<Cell, CacheValue> snapsho
.map(value -> value.value().get())
.collectToMap();
}

@Value.Immutable
interface CacheLookupResult {
Map<Cell, CacheValue> cacheHits();

Set<Cell> missedCells();

static CacheLookupResult of(Map<Cell, CacheValue> cachedValues, Set<Cell> missedCells) {
return ImmutableCacheLookupResult.builder()
.cacheHits(cachedValues)
.missedCells(missedCells)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,30 @@ public Map<Cell, byte[]> get(
return AtlasFutures.getUnchecked(getAsync(tableReference, cells, valueLoader));
}

@Override
public Map<Cell, byte[]> getWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return AtlasFutures.getUnchecked(getAsyncWithCachedRef(tableReference, cells, valueLoader));
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsync(
TableReference tableReference,
Set<Cell> cells,
Function<Set<Cell>, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
return getAsyncWithCachedRef(
tableReference, cells, cacheLookupResult -> valueLoader.apply(cacheLookupResult.missedCells()));
}

@Override
public ListenableFuture<Map<Cell, byte[]>> getAsyncWithCachedRef(
TableReference tableReference,
Set<Cell> cells,
Function<CacheLookupResult, ListenableFuture<Map<Cell, byte[]>>> valueLoader) {
if (shouldValidate()) {
ListenableFuture<Map<Cell, byte[]>> remoteReads = valueLoader.apply(cells);
ListenableFuture<Map<Cell, byte[]>> remoteReads = valueLoader.apply(CacheLookupResult.of(Map.of(), cells));
ListenableFuture<Map<Cell, byte[]>> cacheReads = delegate.getAsync(
tableReference,
cells,
Expand All @@ -110,7 +127,7 @@ public ListenableFuture<Map<Cell, byte[]>> getAsync(
},
MoreExecutors.directExecutor());
} else {
return delegate.getAsync(tableReference, cells, valueLoader);
return delegate.getAsyncWithCachedRef(tableReference, cells, valueLoader);
}
}

Expand Down
Loading