Skip to content

Commit 98a917f

Browse files
Adding hasCommittedMutations flag (#29)
1 parent 7005c97 commit 98a917f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+64475
-36548
lines changed

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,28 @@ public void testCanUpdateAnExistingDocument() {
7272
assertEquals(finalData, doc.getData());
7373
}
7474

75+
@Test
76+
public void testCanUpdateAnUnknownDocument() {
77+
DocumentReference writerRef = testFirestore().collection("collection").document();
78+
DocumentReference readerRef =
79+
testFirestore().collection("collection").document(writerRef.getId());
80+
waitFor(writerRef.set(map("a", "a")));
81+
waitFor(readerRef.update(map("b", "b")));
82+
DocumentSnapshot writerSnap = waitFor(writerRef.get(Source.CACHE));
83+
assertTrue(writerSnap.exists());
84+
try {
85+
waitFor(readerRef.get(Source.CACHE));
86+
fail("Should have thrown exception");
87+
} catch (RuntimeException e) {
88+
assertEquals(
89+
((FirebaseFirestoreException) e.getCause().getCause()).getCode(), Code.UNAVAILABLE);
90+
}
91+
writerSnap = waitFor(writerRef.get());
92+
assertEquals(map("a", "a", "b", "b"), writerSnap.getData());
93+
DocumentSnapshot readerSnap = waitFor(readerRef.get());
94+
assertEquals(map("a", "a", "b", "b"), readerSnap.getData());
95+
}
96+
7597
@Test
7698
public void testCanMergeDataWithAnExistingDocumentUsingSet() {
7799
DocumentReference documentReference = testCollection("rooms").document("eros");

firebase-firestore/src/main/java/com/google/firebase/firestore/Transaction.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.firestore;
1616

1717
import static com.google.common.base.Preconditions.checkNotNull;
18+
import static com.google.firebase.firestore.util.Assert.fail;
1819

1920
import android.support.annotation.NonNull;
2021
import android.support.annotation.Nullable;
@@ -26,7 +27,6 @@
2627
import com.google.firebase.firestore.model.Document;
2728
import com.google.firebase.firestore.model.MaybeDocument;
2829
import com.google.firebase.firestore.model.NoDocument;
29-
import com.google.firebase.firestore.util.Assert;
3030
import com.google.firebase.firestore.util.Executors;
3131
import com.google.firebase.firestore.util.Util;
3232
import java.util.Collections;
@@ -237,15 +237,19 @@ private Task<DocumentSnapshot> getAsync(DocumentReference documentRef) {
237237
}
238238
List<MaybeDocument> docs = task.getResult();
239239
if (docs.size() != 1) {
240-
throw Assert.fail("Mismatch in docs returned from document lookup.");
240+
throw fail("Mismatch in docs returned from document lookup.");
241241
}
242242
MaybeDocument doc = docs.get(0);
243-
if (doc instanceof NoDocument) {
243+
if (doc instanceof Document) {
244+
return DocumentSnapshot.fromDocument(
245+
firestore, (Document) doc, /*fromCache=*/ false);
246+
} else if (doc instanceof NoDocument) {
244247
return DocumentSnapshot.fromNoDocument(
245248
firestore, doc.getKey(), /*fromCache=*/ false);
246249
} else {
247-
return DocumentSnapshot.fromDocument(
248-
firestore, (Document) doc, /*fromCache=*/ false);
250+
throw fail(
251+
"BatchGetDocumentsRequest returned unexpected document type: "
252+
+ doc.getClass().getCanonicalName());
249253
}
250254
});
251255
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.firebase.firestore.FirebaseFirestoreException.Code;
2121
import com.google.firebase.firestore.core.UserData.ParsedSetData;
2222
import com.google.firebase.firestore.core.UserData.ParsedUpdateData;
23+
import com.google.firebase.firestore.model.Document;
2324
import com.google.firebase.firestore.model.DocumentKey;
2425
import com.google.firebase.firestore.model.MaybeDocument;
2526
import com.google.firebase.firestore.model.NoDocument;
@@ -40,6 +41,8 @@
4041
import java.util.concurrent.TimeUnit;
4142
import javax.annotation.Nullable;
4243

44+
import static com.google.firebase.firestore.util.Assert.fail;
45+
4346
/**
4447
* Internal transaction object responsible for accumulating the mutations to perform and the base
4548
* versions for any documents read.
@@ -55,10 +58,14 @@ public Transaction(Datastore d) {
5558
}
5659

5760
private void recordVersion(MaybeDocument doc) throws FirebaseFirestoreException {
58-
SnapshotVersion docVersion = doc.getVersion();
59-
if (doc instanceof NoDocument) {
61+
SnapshotVersion docVersion;
62+
if (doc instanceof Document) {
63+
docVersion = doc.getVersion();
64+
} else if (doc instanceof NoDocument) {
6065
// For nonexistent docs, we must use precondition with version 0 when we overwrite them.
6166
docVersion = SnapshotVersion.NONE;
67+
} else {
68+
throw fail("Unexpected document type in transaction: " + doc.getClass().getCanonicalName());
6269
}
6370

6471
if (readVersions.containsKey(doc.getKey())) {

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
@@ -214,6 +214,7 @@ public <D extends MaybeDocument> DocumentChanges computeDocChanges(
214214
while (newDocumentSet.size() > query.getLimit()) {
215215
Document oldDoc = newDocumentSet.getLastDocument();
216216
newDocumentSet = newDocumentSet.remove(oldDoc.getKey());
217+
newMutatedKeys = newMutatedKeys.remove(oldDoc.getKey());
217218
changeSet.addChange(DocumentViewChange.create(Type.REMOVED, oldDoc));
218219
}
219220
}

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

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.firebase.firestore.model.MaybeDocument;
2525
import com.google.firebase.firestore.model.NoDocument;
2626
import com.google.firebase.firestore.model.SnapshotVersion;
27+
import com.google.firebase.firestore.model.UnknownDocument;
2728
import com.google.firebase.firestore.model.mutation.Mutation;
2829
import com.google.firebase.firestore.model.mutation.MutationBatch;
2930
import com.google.firebase.firestore.model.value.FieldValue;
@@ -48,25 +49,35 @@ com.google.firebase.firestore.proto.MaybeDocument encodeMaybeDocument(MaybeDocum
4849
com.google.firebase.firestore.proto.MaybeDocument.Builder builder =
4950
com.google.firebase.firestore.proto.MaybeDocument.newBuilder();
5051
if (document instanceof NoDocument) {
51-
builder.setNoDocument(encodeNoDocument((NoDocument) document));
52-
52+
NoDocument noDocument = (NoDocument) document;
53+
builder.setNoDocument(encodeNoDocument(noDocument));
54+
builder.setHasCommittedMutations(noDocument.hasCommittedMutations());
5355
} else if (document instanceof Document) {
54-
builder.setDocument(encodeDocument((Document) document));
56+
Document existingDocument = (Document) document;
57+
builder.setDocument(encodeDocument(existingDocument));
58+
builder.setHasCommittedMutations(existingDocument.hasCommittedMutations());
59+
} else if (document instanceof UnknownDocument) {
60+
builder.setUnknownDocument(encodeUnknownDocument((UnknownDocument) document));
61+
builder.setHasCommittedMutations(true);
5562
} else {
5663
throw fail("Unknown document type %s", document.getClass().getCanonicalName());
5764
}
65+
5866
return builder.build();
5967
}
6068

6169
/** Decodes a MaybeDocument proto to the equivalent model. */
6270
MaybeDocument decodeMaybeDocument(com.google.firebase.firestore.proto.MaybeDocument proto) {
6371
switch (proto.getDocumentTypeCase()) {
6472
case DOCUMENT:
65-
return decodeDocument(proto.getDocument());
73+
return decodeDocument(proto.getDocument(), proto.getHasCommittedMutations());
6674

6775
case NO_DOCUMENT:
6876
return decodeNoDocument(proto.getNoDocument());
6977

78+
case UNKNOWN_DOCUMENT:
79+
return decodeUnknownDocument(proto.getUnknownDocument());
80+
7081
default:
7182
throw fail("Unknown MaybeDocument %s", proto);
7283
}
@@ -93,11 +104,18 @@ private com.google.firestore.v1beta1.Document encodeDocument(Document document)
93104
}
94105

95106
/** Decodes a Document proto to the equivalent model. */
96-
private Document decodeDocument(com.google.firestore.v1beta1.Document document) {
107+
private Document decodeDocument(
108+
com.google.firestore.v1beta1.Document document, boolean hasCommittedMutations) {
97109
DocumentKey key = rpcSerializer.decodeKey(document.getName());
98110
ObjectValue value = rpcSerializer.decodeFields(document.getFieldsMap());
99111
SnapshotVersion version = rpcSerializer.decodeVersion(document.getUpdateTime());
100-
return new Document(key, version, value, false);
112+
return new Document(
113+
key,
114+
version,
115+
value,
116+
hasCommittedMutations
117+
? Document.DocumentState.COMMITTED_MUTATIONS
118+
: Document.DocumentState.SYNCED);
101119
}
102120

103121
/** Encodes a NoDocument value to the equivalent proto. */
@@ -116,6 +134,24 @@ private NoDocument decodeNoDocument(com.google.firebase.firestore.proto.NoDocume
116134
return new NoDocument(key, version);
117135
}
118136

137+
/** Encodes a UnknownDocument value to the equivalent proto. */
138+
private com.google.firebase.firestore.proto.UnknownDocument encodeUnknownDocument(
139+
UnknownDocument document) {
140+
com.google.firebase.firestore.proto.UnknownDocument.Builder builder =
141+
com.google.firebase.firestore.proto.UnknownDocument.newBuilder();
142+
builder.setName(rpcSerializer.encodeKey(document.getKey()));
143+
builder.setVersion(rpcSerializer.encodeTimestamp(document.getVersion().getTimestamp()));
144+
return builder.build();
145+
}
146+
147+
/** Decodes a UnknownDocument proto to the equivalent model. */
148+
private UnknownDocument decodeUnknownDocument(
149+
com.google.firebase.firestore.proto.UnknownDocument proto) {
150+
DocumentKey key = rpcSerializer.decodeKey(proto.getName());
151+
SnapshotVersion version = rpcSerializer.decodeVersion(proto.getVersion());
152+
return new UnknownDocument(key, version);
153+
}
154+
119155
/** Encodes a MutationBatch model for local storage in the mutation queue. */
120156
com.google.firebase.firestore.proto.WriteBatch encodeMutationBatch(MutationBatch batch) {
121157
com.google.firebase.firestore.proto.WriteBatch.Builder result =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve
387387
// resolution failing).
388388
if (existingDoc == null
389389
|| doc.getVersion().equals(SnapshotVersion.NONE)
390-
|| authoritativeUpdates.contains(doc.getKey())
390+
|| (authoritativeUpdates.contains(doc.getKey()) && !existingDoc.hasPendingWrites())
391391
|| doc.getVersion().compareTo(existingDoc.getVersion()) >= 0) {
392392
remoteDocuments.add(doc);
393393
} else {

firebase-firestore/src/main/java/com/google/firebase/firestore/model/Document.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@
2525
*/
2626
public class Document extends MaybeDocument {
2727

28+
/** Describes the `hasPendingWrites` state of a document. */
29+
public enum DocumentState {
30+
/** Local mutations applied via the mutation queue. Document is potentially inconsistent. */
31+
LOCAL_MUTATIONS,
32+
/** Mutations applied based on a write acknowledgment. Document is potentially inconsistent. */
33+
COMMITTED_MUTATIONS,
34+
/** No mutations applied. Document was sent to us by Watch. */
35+
SYNCED
36+
}
37+
2838
private static final Comparator<Document> KEY_COMPARATOR =
2939
new Comparator<Document>() {
3040
@Override
@@ -40,13 +50,13 @@ public static Comparator<Document> keyComparator() {
4050

4151
private final ObjectValue data;
4252

43-
private final boolean hasLocalMutations;
53+
private final DocumentState documentState;
4454

4555
public Document(
46-
DocumentKey key, SnapshotVersion version, ObjectValue data, boolean hasLocalMutations) {
56+
DocumentKey key, SnapshotVersion version, ObjectValue data, DocumentState documentState) {
4757
super(key, version);
4858
this.data = data;
49-
this.hasLocalMutations = hasLocalMutations;
59+
this.documentState = documentState;
5060
}
5161

5262
public ObjectValue getData() {
@@ -63,7 +73,16 @@ public ObjectValue getData() {
6373
}
6474

6575
public boolean hasLocalMutations() {
66-
return hasLocalMutations;
76+
return documentState.equals(DocumentState.LOCAL_MUTATIONS);
77+
}
78+
79+
public boolean hasCommittedMutations() {
80+
return documentState.equals(DocumentState.COMMITTED_MUTATIONS);
81+
}
82+
83+
@Override
84+
public boolean hasPendingWrites() {
85+
return this.hasLocalMutations() || this.hasCommittedMutations();
6786
}
6887

6988
@Override
@@ -79,7 +98,7 @@ public boolean equals(Object o) {
7998

8099
return getVersion().equals(document.getVersion())
81100
&& getKey().equals(document.getKey())
82-
&& hasLocalMutations == document.hasLocalMutations
101+
&& documentState.equals(document.documentState)
83102
&& data.equals(document.data);
84103
}
85104

@@ -88,7 +107,7 @@ public int hashCode() {
88107
int result = getKey().hashCode();
89108
result = 31 * result + data.hashCode();
90109
result = 31 * result + getVersion().hashCode();
91-
result = 31 * result + (hasLocalMutations ? 1 : 0);
110+
result = 31 * result + documentState.hashCode();
92111
return result;
93112
}
94113

@@ -101,8 +120,8 @@ public String toString() {
101120
+ data
102121
+ ", version="
103122
+ getVersion()
104-
+ ", hasLocalMutations="
105-
+ hasLocalMutations
123+
+ ", documentState="
124+
+ documentState.name()
106125
+ '}';
107126
}
108127
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/MaybeDocument.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,9 @@ public DocumentKey getKey() {
4141
public SnapshotVersion getVersion() {
4242
return version;
4343
}
44+
45+
/**
46+
* Whether this document has a local mutation applied that has not yet been acknowledged by Watch.
47+
*/
48+
public abstract boolean hasPendingWrites();
4449
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/NoDocument.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ public boolean equals(Object o) {
3535
return getVersion().equals(that.getVersion()) && getKey().equals(that.getKey());
3636
}
3737

38+
@Override
39+
public boolean hasPendingWrites() {
40+
return hasCommittedMutations();
41+
}
42+
43+
public boolean hasCommittedMutations() {
44+
// We currently don't raise `hasPendingWrites` for deleted documents, even if Watch hasn't
45+
// caught up to the deleted version yet.
46+
return false;
47+
}
48+
3849
@Override
3950
public int hashCode() {
4051
int result = getKey().hashCode();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.model;
16+
17+
/**
18+
* A class representing an existing document whose data is unknown (e.g. a document that was updated
19+
* without a known base document).
20+
*/
21+
public class UnknownDocument extends MaybeDocument {
22+
public UnknownDocument(DocumentKey key, SnapshotVersion version) {
23+
super(key, version);
24+
}
25+
26+
@Override
27+
public boolean equals(Object o) {
28+
if (this == o) {
29+
return true;
30+
}
31+
if (o == null || getClass() != o.getClass()) {
32+
return false;
33+
}
34+
35+
UnknownDocument that = (UnknownDocument) o;
36+
37+
return getVersion().equals(that.getVersion()) && getKey().equals(that.getKey());
38+
}
39+
40+
@Override
41+
public boolean hasPendingWrites() {
42+
return true;
43+
}
44+
45+
@Override
46+
public int hashCode() {
47+
int result = getKey().hashCode();
48+
result = 31 * result + getVersion().hashCode();
49+
return result;
50+
}
51+
52+
@Override
53+
public String toString() {
54+
return "UnknownDocument{key=" + getKey() + ", version=" + getVersion() + '}';
55+
}
56+
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/DeleteMutation.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ public MaybeDocument applyToRemoteDocument(
6565
// Unlike applyToLocalView, if we're applying a mutation to a remote document the server has
6666
// accepted the mutation so the precondition must have held.
6767

68-
return new NoDocument(getKey(), SnapshotVersion.NONE);
68+
// We store the deleted document at the commit version of the delete. Any document version
69+
// that the server sends us before the delete was applied is discarded
70+
return new NoDocument(getKey(), mutationResult.getVersion());
6971
}
7072

7173
@Nullable

0 commit comments

Comments
 (0)