Skip to content

Commit 20833d6

Browse files
Persist a Query's Sync State
1 parent 3d2d896 commit 20833d6

File tree

22 files changed

+174
-20
lines changed

22 files changed

+174
-20
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/core/QueryListener.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public void onViewSnapshot(ViewSnapshot newSnapshot) {
8080
documentChanges,
8181
newSnapshot.isFromCache(),
8282
newSnapshot.getMutatedKeys(),
83+
newSnapshot.isSynced(),
8384
newSnapshot.didSyncStateChange(),
8485
/* excludesMetadataChanges= */ true);
8586
}
@@ -158,6 +159,7 @@ private void raiseInitialEvent(ViewSnapshot snapshot) {
158159
snapshot.getDocuments(),
159160
snapshot.getMutatedKeys(),
160161
snapshot.isFromCache(),
162+
snapshot.isSynced(),
161163
snapshot.excludesMetadataChanges());
162164
raisedInitialEvent = true;
163165
listener.onEvent(snapshot, null);

firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ private void emitNewSnapsAndNotifyLocalStore(
488488
// The query has a limit and some docs were removed/updated, so we need to re-run the query
489489
// against the local store to make sure we didn't lose any good docs that had been past the
490490
// limit.
491+
localStore.markNeedsRefill(queryView.getTargetId());
491492
ImmutableSortedMap<DocumentKey, Document> docs =
492493
localStore.executeQuery(queryView.getQuery());
493494
viewDocChanges = view.computeDocChanges(docs, viewDocChanges);

firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh
311311
viewChanges,
312312
fromCache,
313313
docChanges.mutatedKeys,
314+
synced,
314315
syncStatedChanged,
315316
/* excludesMetadataChanges= */ false);
316317
}

firebase-firestore/src/main/java/com/google/firebase/firestore/core/ViewSnapshot.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public enum SyncState {
3737
private final List<DocumentViewChange> changes;
3838
private final boolean isFromCache;
3939
private final ImmutableSortedSet<DocumentKey> mutatedKeys;
40+
private final boolean synced;
4041
private final boolean didSyncStateChange;
4142
private boolean excludesMetadataChanges;
4243

@@ -47,6 +48,7 @@ public ViewSnapshot(
4748
List<DocumentViewChange> changes,
4849
boolean isFromCache,
4950
ImmutableSortedSet<DocumentKey> mutatedKeys,
51+
boolean synced,
5052
boolean didSyncStateChange,
5153
boolean excludesMetadataChanges) {
5254
this.query = query;
@@ -55,6 +57,7 @@ public ViewSnapshot(
5557
this.changes = changes;
5658
this.isFromCache = isFromCache;
5759
this.mutatedKeys = mutatedKeys;
60+
this.synced = synced;
5861
this.didSyncStateChange = didSyncStateChange;
5962
this.excludesMetadataChanges = excludesMetadataChanges;
6063
}
@@ -65,6 +68,7 @@ public static ViewSnapshot fromInitialDocuments(
6568
DocumentSet documents,
6669
ImmutableSortedSet<DocumentKey> mutatedKeys,
6770
boolean fromCache,
71+
boolean synced,
6872
boolean excludesMetadataChanges) {
6973
List<DocumentViewChange> viewChanges = new ArrayList<>();
7074
for (Document doc : documents) {
@@ -77,6 +81,7 @@ public static ViewSnapshot fromInitialDocuments(
7781
viewChanges,
7882
fromCache,
7983
mutatedKeys,
84+
synced,
8085
/* didSyncStateChange= */ true,
8186
excludesMetadataChanges);
8287
}
@@ -85,6 +90,14 @@ public Query getQuery() {
8590
return query;
8691
}
8792

93+
/**
94+
* Returns whether the view was synced with the backend at the time the snapshot was raised. A
95+
* synced view is marked CURRENT and contains no Limbo documents.
96+
*/
97+
public boolean isSynced() {
98+
return synced;
99+
}
100+
88101
public DocumentSet getDocuments() {
89102
return documents;
90103
}
@@ -131,6 +144,9 @@ public final boolean equals(Object o) {
131144
if (isFromCache != that.isFromCache) {
132145
return false;
133146
}
147+
if (synced != that.synced) {
148+
return false;
149+
}
134150
if (didSyncStateChange != that.didSyncStateChange) {
135151
return false;
136152
}
@@ -160,6 +176,7 @@ public int hashCode() {
160176
result = 31 * result + changes.hashCode();
161177
result = 31 * result + mutatedKeys.hashCode();
162178
result = 31 * result + (isFromCache ? 1 : 0);
179+
result = 31 * result + (synced ? 1 : 0);
163180
result = 31 * result + (didSyncStateChange ? 1 : 0);
164181
result = 31 * result + (excludesMetadataChanges ? 1 : 0);
165182
return result;
@@ -179,6 +196,8 @@ public String toString() {
179196
+ isFromCache
180197
+ ", mutatedKeys="
181198
+ mutatedKeys.size()
199+
+ ", synced="
200+
+ synced
182201
+ ", didSyncStateChange="
183202
+ didSyncStateChange
184203
+ ", excludesMetadataChanges="

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalSerializer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ com.google.firebase.firestore.proto.Target encodeQueryData(QueryData queryData)
205205
result
206206
.setTargetId(queryData.getTargetId())
207207
.setLastListenSequenceNumber(queryData.getSequenceNumber())
208+
.setSynced(queryData.isSynced())
208209
.setSnapshotVersion(rpcSerializer.encodeVersion(queryData.getSnapshotVersion()))
209210
.setResumeToken(queryData.getResumeToken());
210211

@@ -223,6 +224,7 @@ QueryData decodeQueryData(com.google.firebase.firestore.proto.Target target) {
223224
SnapshotVersion version = rpcSerializer.decodeVersion(target.getSnapshotVersion());
224225
ByteString resumeToken = target.getResumeToken();
225226
long sequenceNumber = target.getLastListenSequenceNumber();
227+
boolean synced = target.getSynced();
226228

227229
Query query;
228230
switch (target.getTargetTypeCase()) {
@@ -239,6 +241,6 @@ QueryData decodeQueryData(com.google.firebase.firestore.proto.Target target) {
239241
}
240242

241243
return new QueryData(
242-
query, targetId, sequenceNumber, QueryPurpose.LISTEN, version, resumeToken);
244+
query, targetId, sequenceNumber, synced, QueryPurpose.LISTEN, version, resumeToken);
243245
}
244246
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalStore.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,11 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve
359359
if (!resumeToken.isEmpty()) {
360360
QueryData oldQueryData = queryData;
361361
queryData =
362-
queryData.copy(remoteEvent.getSnapshotVersion(), resumeToken, sequenceNumber);
362+
queryData.copy(
363+
remoteEvent.getSnapshotVersion(),
364+
resumeToken,
365+
sequenceNumber,
366+
oldQueryData.isSynced());
363367
targetIds.put(boxedTargetId, queryData);
364368

365369
if (shouldPersistQueryData(oldQueryData, queryData, change)) {
@@ -469,12 +473,26 @@ public void notifyLocalViewChanges(List<LocalViewChanges> viewChanges) {
469473
"notifyLocalViewChanges",
470474
() -> {
471475
for (LocalViewChanges viewChange : viewChanges) {
472-
localViewReferences.addReferences(viewChange.getAdded(), viewChange.getTargetId());
476+
int targetId = viewChange.getTargetId();
477+
478+
// Update the local view references for LRU garbage collection
479+
localViewReferences.addReferences(viewChange.getAdded(), targetId);
473480
ImmutableSortedSet<DocumentKey> removed = viewChange.getRemoved();
474481
for (DocumentKey key : removed) {
475482
persistence.getReferenceDelegate().removeReference(key);
476483
}
477-
localViewReferences.removeReferences(removed, viewChange.getTargetId());
484+
localViewReferences.removeReferences(removed, targetId);
485+
486+
// Mark whether the local query view is in sync with the backend.
487+
QueryData queryData = targetIds.get(targetId);
488+
hardAssert(queryData != null, "Can't mark inactive target synchronized: %s", targetId);
489+
QueryData updatedQueryData =
490+
queryData.copy(
491+
queryData.getSnapshotVersion(),
492+
queryData.getResumeToken(),
493+
queryData.getSequenceNumber(),
494+
viewChange.isSynced());
495+
targetIds.put(targetId, updatedQueryData);
478496
}
479497
});
480498
}
@@ -548,10 +566,18 @@ public void releaseQuery(Query query) {
548566

549567
int targetId = queryData.getTargetId();
550568
QueryData cachedQueryData = targetIds.get(targetId);
569+
570+
boolean needsUpdate = false;
551571
if (cachedQueryData.getSnapshotVersion().compareTo(queryData.getSnapshotVersion()) > 0) {
552572
// If we've been avoiding persisting the resumeToken (see shouldPersistQueryData for
553573
// conditions and rationale) we need to persist the token now because there will no
554574
// longer be an in-memory version to fall back on.
575+
needsUpdate = true;
576+
} else if (cachedQueryData.isSynced() != queryData.isSynced()) {
577+
needsUpdate = true;
578+
}
579+
580+
if (needsUpdate) {
555581
queryData = cachedQueryData;
556582
queryCache.updateQueryData(queryData);
557583
}
@@ -570,6 +596,22 @@ public void releaseQuery(Query query) {
570596
});
571597
}
572598

599+
/**
600+
* Mark the query as not-consistent with the backend, indicating that we have to re-run the query
601+
* against the full set of local documents.
602+
*/
603+
public void markNeedsRefill(int targetId) {
604+
QueryData queryData = targetIds.get(targetId);
605+
hardAssert(queryData != null, "Can't mark inactive target: %s", targetId);
606+
QueryData updatedQueryData =
607+
queryData.copy(
608+
queryData.getSnapshotVersion(),
609+
queryData.getResumeToken(),
610+
queryData.getSequenceNumber(),
611+
/* synced= */ false);
612+
targetIds.put(targetId, updatedQueryData);
613+
}
614+
573615
/** Runs the given query against all the documents in the local store and returns the results. */
574616
public ImmutableSortedMap<DocumentKey, Document> executeQuery(Query query) {
575617
return queryEngine.getDocumentsMatchingQuery(query);

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalViewChanges.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,22 @@ public static LocalViewChanges fromViewSnapshot(int targetId, ViewSnapshot snaps
4949
}
5050
}
5151

52-
return new LocalViewChanges(targetId, addedKeys, removedKeys);
52+
return new LocalViewChanges(targetId, snapshot.isSynced(), addedKeys, removedKeys);
5353
}
5454

5555
private final int targetId;
56+
private final boolean synced;
5657

5758
private final ImmutableSortedSet<DocumentKey> added;
5859
private final ImmutableSortedSet<DocumentKey> removed;
5960

6061
public LocalViewChanges(
6162
int targetId,
63+
boolean synced,
6264
ImmutableSortedSet<DocumentKey> added,
6365
ImmutableSortedSet<DocumentKey> removed) {
6466
this.targetId = targetId;
67+
this.synced = synced;
6568
this.added = added;
6669
this.removed = removed;
6770
}
@@ -70,6 +73,11 @@ public int getTargetId() {
7073
return targetId;
7174
}
7275

76+
/** Returns whether the local query view is in sync with the backend. * */
77+
public boolean isSynced() {
78+
return synced;
79+
}
80+
7381
public ImmutableSortedSet<DocumentKey> getAdded() {
7482
return added;
7583
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MemoryLruReferenceDelegate.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ public void removeMutationReference(DocumentKey key) {
137137
public void removeTarget(QueryData queryData) {
138138
QueryData updated =
139139
queryData.copy(
140-
queryData.getSnapshotVersion(), queryData.getResumeToken(), getCurrentSequenceNumber());
140+
queryData.getSnapshotVersion(),
141+
queryData.getResumeToken(),
142+
getCurrentSequenceNumber(),
143+
queryData.isSynced());
141144
persistence.getQueryCache().updateQueryData(updated);
142145
}
143146

firebase-firestore/src/main/java/com/google/firebase/firestore/local/QueryData.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class QueryData {
2626
private final Query query;
2727
private final int targetId;
2828
private final long sequenceNumber;
29+
private final boolean synced;
2930
private final QueryPurpose purpose;
3031
private final SnapshotVersion snapshotVersion;
3132
private final ByteString resumeToken;
@@ -36,6 +37,8 @@ public final class QueryData {
3637
* @param query The query being listened to.
3738
* @param targetId The target to which the query corresponds, assigned by the LocalStore for user
3839
* queries or the SyncEngine for limbo queries.
40+
* @param sequenceNumber The sequence number, denoting the last time this query was used.
41+
* @param synced Whether the query's local state is in sync with the backend.
3942
* @param purpose The purpose of the query.
4043
* @param snapshotVersion The latest snapshot version seen for this target.
4144
* @param resumeToken An opaque, server-assigned token that allows watching a query to be resumed
@@ -47,12 +50,14 @@ public QueryData(
4750
Query query,
4851
int targetId,
4952
long sequenceNumber,
53+
boolean synced,
5054
QueryPurpose purpose,
5155
SnapshotVersion snapshotVersion,
5256
ByteString resumeToken) {
5357
this.query = checkNotNull(query);
5458
this.targetId = targetId;
5559
this.sequenceNumber = sequenceNumber;
60+
this.synced = synced;
5661
this.purpose = purpose;
5762
this.snapshotVersion = checkNotNull(snapshotVersion);
5863
this.resumeToken = checkNotNull(resumeToken);
@@ -64,6 +69,7 @@ public QueryData(Query query, int targetId, long sequenceNumber, QueryPurpose pu
6469
query,
6570
targetId,
6671
sequenceNumber,
72+
/* synced= */ false,
6773
purpose,
6874
SnapshotVersion.NONE,
6975
WatchStream.EMPTY_RESUME_TOKEN);
@@ -93,6 +99,11 @@ public ByteString getResumeToken() {
9399
return resumeToken;
94100
}
95101

102+
/** Whether the query's local state is in sync with the backend. */
103+
public boolean isSynced() {
104+
return synced;
105+
}
106+
96107
@Override
97108
public boolean equals(Object o) {
98109
if (this == o) {
@@ -106,6 +117,7 @@ public boolean equals(Object o) {
106117
return query.equals(queryData.query)
107118
&& targetId == queryData.targetId
108119
&& sequenceNumber == queryData.sequenceNumber
120+
&& synced == queryData.synced
109121
&& purpose.equals(queryData.purpose)
110122
&& snapshotVersion.equals(queryData.snapshotVersion)
111123
&& resumeToken.equals(queryData.resumeToken);
@@ -116,6 +128,7 @@ public int hashCode() {
116128
int result = query.hashCode();
117129
result = 31 * result + targetId;
118130
result = 31 * result + (int) sequenceNumber;
131+
result = 31 * result + (synced ? 1 : 0);
119132
result = 31 * result + purpose.hashCode();
120133
result = 31 * result + snapshotVersion.hashCode();
121134
result = 31 * result + resumeToken.hashCode();
@@ -131,6 +144,8 @@ public String toString() {
131144
+ targetId
132145
+ ", sequenceNumber="
133146
+ sequenceNumber
147+
+ ", isSynced="
148+
+ synced
134149
+ ", purpose="
135150
+ purpose
136151
+ ", snapshotVersion="
@@ -140,9 +155,16 @@ public String toString() {
140155
+ '}';
141156
}
142157

143-
/** Creates a new query data instance with an updated snapshot version and resume token. */
158+
/**
159+
* Creates a new query data instance with an updated snapshot version, sequence number, sync
160+
* status and resume token.
161+
*/
144162
public QueryData copy(
145-
SnapshotVersion snapshotVersion, ByteString resumeToken, long sequenceNumber) {
146-
return new QueryData(query, targetId, sequenceNumber, purpose, snapshotVersion, resumeToken);
163+
SnapshotVersion snapshotVersion,
164+
ByteString resumeToken,
165+
long sequenceNumber,
166+
boolean synced) {
167+
return new QueryData(
168+
query, targetId, sequenceNumber, synced, purpose, snapshotVersion, resumeToken);
147169
}
148170
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteLruReferenceDelegate.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ public int removeOrphanedDocuments(long upperBound) {
169169
public void removeTarget(QueryData queryData) {
170170
QueryData updated =
171171
queryData.copy(
172-
queryData.getSnapshotVersion(), queryData.getResumeToken(), getCurrentSequenceNumber());
172+
queryData.getSnapshotVersion(),
173+
queryData.getResumeToken(),
174+
getCurrentSequenceNumber(),
175+
queryData.isSynced());
173176
persistence.getQueryCache().updateQueryData(updated);
174177
}
175178

0 commit comments

Comments
 (0)