Skip to content

Commit 42ec7f1

Browse files
Index-Free (3/6): Persist a Query's Sync State (#616)
1 parent a2d868c commit 42ec7f1

File tree

19 files changed

+181
-14
lines changed

19 files changed

+181
-14
lines changed

firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ public static QuerySnapshot querySnapshot(
117117
documentChanges,
118118
isFromCache,
119119
mutatedKeys,
120-
true,
120+
/* hasLimboDocuments= */ false,
121+
/* didSyncStateChange= */ true,
121122
/* excludesMetadataChanges= */ false);
122123
return new QuerySnapshot(query(path), viewSnapshot, FIRESTORE);
123124
}

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.hasLimboDocuments(),
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.hasLimboDocuments(),
161163
snapshot.excludesMetadataChanges());
162164
raisedInitialEvent = true;
163165
listener.onEvent(snapshot, null);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,8 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh
296296
});
297297
applyTargetChange(targetChange);
298298
List<LimboDocumentChange> limboDocumentChanges = updateLimboDocuments();
299-
boolean synced = limboDocuments.size() == 0 && current;
299+
boolean hasLimboDocuments = !(limboDocuments.size() == 0);
300+
boolean synced = !hasLimboDocuments && current;
300301
SyncState newSyncState = synced ? SyncState.SYNCED : SyncState.LOCAL;
301302
boolean syncStatedChanged = newSyncState != syncState;
302303
syncState = newSyncState;
@@ -311,6 +312,7 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh
311312
viewChanges,
312313
fromCache,
313314
docChanges.mutatedKeys,
315+
hasLimboDocuments,
314316
syncStatedChanged,
315317
/* excludesMetadataChanges= */ false);
316318
}

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

Lines changed: 15 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 hasLimboDocuments;
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 hasLimboDocuments,
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.hasLimboDocuments = hasLimboDocuments;
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 hasLimboDocuments,
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+
hasLimboDocuments,
8085
/* didSyncStateChange= */ true,
8186
excludesMetadataChanges);
8287
}
@@ -85,6 +90,10 @@ public Query getQuery() {
8590
return query;
8691
}
8792

93+
public boolean hasLimboDocuments() {
94+
return hasLimboDocuments;
95+
}
96+
8897
public DocumentSet getDocuments() {
8998
return documents;
9099
}
@@ -131,6 +140,9 @@ public final boolean equals(Object o) {
131140
if (isFromCache != that.isFromCache) {
132141
return false;
133142
}
143+
if (hasLimboDocuments != that.hasLimboDocuments) {
144+
return false;
145+
}
134146
if (didSyncStateChange != that.didSyncStateChange) {
135147
return false;
136148
}
@@ -160,6 +172,7 @@ public int hashCode() {
160172
result = 31 * result + changes.hashCode();
161173
result = 31 * result + mutatedKeys.hashCode();
162174
result = 31 * result + (isFromCache ? 1 : 0);
175+
result = 31 * result + (hasLimboDocuments ? 1 : 0);
163176
result = 31 * result + (didSyncStateChange ? 1 : 0);
164177
result = 31 * result + (excludesMetadataChanges ? 1 : 0);
165178
return result;
@@ -179,6 +192,8 @@ public String toString() {
179192
+ isFromCache
180193
+ ", mutatedKeys="
181194
+ mutatedKeys.size()
195+
+ ", hasLimboDocuments="
196+
+ hasLimboDocuments
182197
+ ", didSyncStateChange="
183198
+ didSyncStateChange
184199
+ ", excludesMetadataChanges="

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ com.google.firebase.firestore.proto.Target encodeQueryData(QueryData queryData)
205205
result
206206
.setTargetId(queryData.getTargetId())
207207
.setLastListenSequenceNumber(queryData.getSequenceNumber())
208+
.setLastLimboFreeSnapshotVersion(
209+
rpcSerializer.encodeVersion(queryData.getLastLimboFreeSnapshotVersion()))
208210
.setSnapshotVersion(rpcSerializer.encodeVersion(queryData.getSnapshotVersion()))
209211
.setResumeToken(queryData.getResumeToken());
210212

@@ -221,6 +223,8 @@ com.google.firebase.firestore.proto.Target encodeQueryData(QueryData queryData)
221223
QueryData decodeQueryData(com.google.firebase.firestore.proto.Target target) {
222224
int targetId = target.getTargetId();
223225
SnapshotVersion version = rpcSerializer.decodeVersion(target.getSnapshotVersion());
226+
SnapshotVersion lastLimboFreeSnapshotVersion =
227+
rpcSerializer.decodeVersion(target.getLastLimboFreeSnapshotVersion());
224228
ByteString resumeToken = target.getResumeToken();
225229
long sequenceNumber = target.getLastListenSequenceNumber();
226230

@@ -239,6 +243,12 @@ QueryData decodeQueryData(com.google.firebase.firestore.proto.Target target) {
239243
}
240244

241245
return new QueryData(
242-
query, targetId, sequenceNumber, QueryPurpose.LISTEN, version, resumeToken);
246+
query,
247+
targetId,
248+
sequenceNumber,
249+
QueryPurpose.LISTEN,
250+
version,
251+
lastLimboFreeSnapshotVersion,
252+
resumeToken);
243253
}
244254
}

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,35 @@ public void notifyLocalViewChanges(List<LocalViewChanges> viewChanges) {
469469
"notifyLocalViewChanges",
470470
() -> {
471471
for (LocalViewChanges viewChange : viewChanges) {
472-
localViewReferences.addReferences(viewChange.getAdded(), viewChange.getTargetId());
472+
int targetId = viewChange.getTargetId();
473+
474+
localViewReferences.addReferences(viewChange.getAdded(), targetId);
473475
ImmutableSortedSet<DocumentKey> removed = viewChange.getRemoved();
474476
for (DocumentKey key : removed) {
475477
persistence.getReferenceDelegate().removeReference(key);
476478
}
477-
localViewReferences.removeReferences(removed, viewChange.getTargetId());
479+
localViewReferences.removeReferences(removed, targetId);
480+
481+
if (!viewChange.hasUnresolvedLimboDocuments()) {
482+
QueryData queryData = targetIds.get(targetId);
483+
hardAssert(
484+
queryData != null,
485+
"Can't set limbo-free snapshot version for unknown target: %s",
486+
targetId);
487+
488+
// Advance the last limbo free snapshot version
489+
SnapshotVersion lastLimboFreeSnapshotVersion = queryData.getSnapshotVersion();
490+
QueryData updatedQueryData =
491+
new QueryData(
492+
queryData.getQuery(),
493+
queryData.getTargetId(),
494+
queryData.getSequenceNumber(),
495+
queryData.getPurpose(),
496+
queryData.getSnapshotVersion(),
497+
lastLimboFreeSnapshotVersion,
498+
queryData.getResumeToken());
499+
targetIds.put(targetId, updatedQueryData);
500+
}
478501
}
479502
});
480503
}
@@ -548,10 +571,20 @@ public void releaseQuery(Query query) {
548571

549572
int targetId = queryData.getTargetId();
550573
QueryData cachedQueryData = targetIds.get(targetId);
574+
575+
boolean needsUpdate = false;
551576
if (cachedQueryData.getSnapshotVersion().compareTo(queryData.getSnapshotVersion()) > 0) {
552577
// If we've been avoiding persisting the resumeToken (see shouldPersistQueryData for
553578
// conditions and rationale) we need to persist the token now because there will no
554579
// longer be an in-memory version to fall back on.
580+
needsUpdate = true;
581+
} else if (!cachedQueryData
582+
.getLastLimboFreeSnapshotVersion()
583+
.equals(queryData.getLastLimboFreeSnapshotVersion())) {
584+
needsUpdate = true;
585+
}
586+
587+
if (needsUpdate) {
555588
queryData = cachedQueryData;
556589
queryCache.updateQueryData(queryData);
557590
}

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

Lines changed: 12 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.hasLimboDocuments(), addedKeys, removedKeys);
5353
}
5454

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

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

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

76+
/**
77+
* Returns whether there were any unresolved limbo documents in the corresponding view when this
78+
* change was computed.
79+
*/
80+
public boolean hasUnresolvedLimboDocuments() {
81+
return hasUnresolvedLimboDocuments;
82+
}
83+
7384
public ImmutableSortedSet<DocumentKey> getAdded() {
7485
return added;
7586
}

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public final class QueryData {
2828
private final long sequenceNumber;
2929
private final QueryPurpose purpose;
3030
private final SnapshotVersion snapshotVersion;
31+
private final SnapshotVersion lastLimboFreeSnapshotVersion;
3132
private final ByteString resumeToken;
3233

3334
/**
@@ -36,23 +37,27 @@ 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.
3941
* @param purpose The purpose of the query.
4042
* @param snapshotVersion The latest snapshot version seen for this target.
43+
* @param lastLimboFreeSnapshotVersion The maximum snapshot version at which the associated query
44+
* view contained no limbo documents.
4145
* @param resumeToken An opaque, server-assigned token that allows watching a query to be resumed
4246
* after disconnecting without retransmitting all the data that matches the query. The resume
4347
* token essentially identifies a point in time from which the server should resume sending
44-
* results.
4548
*/
4649
public QueryData(
4750
Query query,
4851
int targetId,
4952
long sequenceNumber,
5053
QueryPurpose purpose,
5154
SnapshotVersion snapshotVersion,
55+
SnapshotVersion lastLimboFreeSnapshotVersion,
5256
ByteString resumeToken) {
5357
this.query = checkNotNull(query);
5458
this.targetId = targetId;
5559
this.sequenceNumber = sequenceNumber;
60+
this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
5661
this.purpose = purpose;
5762
this.snapshotVersion = checkNotNull(snapshotVersion);
5863
this.resumeToken = checkNotNull(resumeToken);
@@ -66,6 +71,7 @@ public QueryData(Query query, int targetId, long sequenceNumber, QueryPurpose pu
6671
sequenceNumber,
6772
purpose,
6873
SnapshotVersion.NONE,
74+
SnapshotVersion.NONE,
6975
WatchStream.EMPTY_RESUME_TOKEN);
7076
}
7177

@@ -93,6 +99,13 @@ public ByteString getResumeToken() {
9399
return resumeToken;
94100
}
95101

102+
/**
103+
* Returns the last snapshot version for which the associated view contained no limbo documents.
104+
*/
105+
public SnapshotVersion getLastLimboFreeSnapshotVersion() {
106+
return lastLimboFreeSnapshotVersion;
107+
}
108+
96109
@Override
97110
public boolean equals(Object o) {
98111
if (this == o) {
@@ -108,6 +121,7 @@ public boolean equals(Object o) {
108121
&& sequenceNumber == queryData.sequenceNumber
109122
&& purpose.equals(queryData.purpose)
110123
&& snapshotVersion.equals(queryData.snapshotVersion)
124+
&& lastLimboFreeSnapshotVersion.equals(queryData.lastLimboFreeSnapshotVersion)
111125
&& resumeToken.equals(queryData.resumeToken);
112126
}
113127

@@ -118,6 +132,7 @@ public int hashCode() {
118132
result = 31 * result + (int) sequenceNumber;
119133
result = 31 * result + purpose.hashCode();
120134
result = 31 * result + snapshotVersion.hashCode();
135+
result = 31 * result + lastLimboFreeSnapshotVersion.hashCode();
121136
result = 31 * result + resumeToken.hashCode();
122137
return result;
123138
}
@@ -135,6 +150,8 @@ public String toString() {
135150
+ purpose
136151
+ ", snapshotVersion="
137152
+ snapshotVersion
153+
+ ", lastLimboFreeSnapshotVersion="
154+
+ lastLimboFreeSnapshotVersion
138155
+ ", resumeToken="
139156
+ resumeToken
140157
+ '}';
@@ -143,6 +160,13 @@ public String toString() {
143160
/** Creates a new query data instance with an updated snapshot version and resume token. */
144161
public QueryData copy(
145162
SnapshotVersion snapshotVersion, ByteString resumeToken, long sequenceNumber) {
146-
return new QueryData(query, targetId, sequenceNumber, purpose, snapshotVersion, resumeToken);
163+
return new QueryData(
164+
query,
165+
targetId,
166+
sequenceNumber,
167+
purpose,
168+
snapshotVersion,
169+
lastLimboFreeSnapshotVersion,
170+
resumeToken);
147171
}
148172
}

firebase-firestore/src/proto/google/firebase/firestore/proto/target.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ message Target {
7878
// A target specified by a set of document names.
7979
google.firestore.v1.Target.DocumentsTarget documents = 6;
8080
}
81+
82+
// Denotes the maximum snapshot version at which the associated querye view
83+
// contained no limbo documents.
84+
google.protobuf.Timestamp last_limbo_free_snapshot_version = 7;
8185
}
8286

8387
// Global state tracked across all Targets, tracked separately to avoid the

firebase-firestore/src/roboUtil/java/com/google/firebase/firestore/TestUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ public static QuerySnapshot querySnapshot(
129129
documentChanges,
130130
isFromCache,
131131
mutatedKeys,
132-
true,
132+
/* hasLimboDocuments= */ false,
133+
/* didSyncStateChange= */ true,
133134
/* excludesMetadataChanges= */ false);
134135
return new QuerySnapshot(query(path), viewSnapshot, FIRESTORE);
135136
}

firebase-firestore/src/test/java/com/google/firebase/firestore/QuerySnapshotTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public void testIncludeMetadataChanges() {
125125
documentChanges,
126126
/*isFromCache=*/ false,
127127
/*mutatedKeys=*/ keySet(),
128+
/*hasLimboDocuments=*/ true,
128129
/*didSyncStateChange=*/ true,
129130
/* excludesMetadataChanges= */ false);
130131

0 commit comments

Comments
 (0)