Skip to content

Commit 96a4b39

Browse files
Add largest batch ID (#3331)
1 parent 275b3eb commit 96a4b39

File tree

8 files changed

+58
-46
lines changed

8 files changed

+58
-46
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ private ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingCollection
260260
Query query, IndexOffset offset) {
261261
Map<DocumentKey, MutableDocument> remoteDocuments =
262262
remoteDocumentCache.getAll(query.getPath(), offset);
263-
Map<DocumentKey, Overlay> overlays = documentOverlayCache.getOverlays(query.getPath(), -1);
263+
Map<DocumentKey, Overlay> overlays =
264+
documentOverlayCache.getOverlays(query.getPath(), offset.getLargestBatchId());
264265

265266
// As documents might match the query because of their overlay we need to include documents
266267
// for all overlays in the initial document set.

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

+10-5
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ public void start() {
106106
// Fetch all index states if persisted for the user. These states contain per user information
107107
// on how up to date the index is.
108108
db.query(
109-
"SELECT index_id, sequence_number, read_time_seconds, read_time_nanos, document_key "
110-
+ "FROM index_state WHERE uid = ?")
109+
"SELECT index_id, sequence_number, read_time_seconds, read_time_nanos, document_key, "
110+
+ "largest_batch_id FROM index_state WHERE uid = ?")
111111
.binding(uid)
112112
.forEach(
113113
row -> {
@@ -117,8 +117,11 @@ public void start() {
117117
new SnapshotVersion(new Timestamp(row.getLong(2), row.getInt(3)));
118118
DocumentKey documentKey =
119119
DocumentKey.fromPath(EncodedPath.decodeResourcePath(row.getString(4)));
120+
int largestBatchId = row.getInt(5);
120121
indexStates.put(
121-
indexId, FieldIndex.IndexState.create(sequenceNumber, readTime, documentKey));
122+
indexId,
123+
FieldIndex.IndexState.create(
124+
sequenceNumber, readTime, documentKey, largestBatchId));
122125
});
123126

124127
// Fetch all indices and combine with user's index state if available.
@@ -660,13 +663,15 @@ public void updateCollectionGroup(String collectionGroup, FieldIndex.IndexOffset
660663
FieldIndex.IndexState.create(memoizedMaxSequenceNumber, offset));
661664
db.execute(
662665
"REPLACE INTO index_state (index_id, uid, sequence_number, "
663-
+ "read_time_seconds, read_time_nanos, document_key) VALUES(?, ?, ?, ?, ?, ?)",
666+
+ "read_time_seconds, read_time_nanos, document_key, largest_batch_id) "
667+
+ "VALUES(?, ?, ?, ?, ?, ?, ?)",
664668
fieldIndex.getIndexId(),
665669
uid,
666670
memoizedMaxSequenceNumber,
667671
offset.getReadTime().getTimestamp().getSeconds(),
668672
offset.getReadTime().getTimestamp().getNanoseconds(),
669-
EncodedPath.encode(offset.getDocumentKey().getPath()));
673+
EncodedPath.encode(offset.getDocumentKey().getPath()),
674+
offset.getLargestBatchId());
670675
memoizeIndex(updatedIndex);
671676
}
672677
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ private void createFieldIndex() {
398398
+ "read_time_seconds INTEGER, " // Read time of last processed document
399399
+ "read_time_nanos INTEGER, "
400400
+ "document_key TEXT, " // Key of the last processed document
401+
+ "largest_batch_id INTEGER, " // Largest mutation batch id that was processed
401402
+ "PRIMARY KEY (index_id, uid))");
402403

403404
// The index entry table stores the encoded entries for all fields.

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

+27-11
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ public abstract class FieldIndex {
3939
/** An ID for an index that has not yet been added to persistence. */
4040
public static final int UNKNOWN_ID = -1;
4141

42+
/** The initial mutation batch id for each index. Gets updated during index backfill. */
43+
public static final int INITIAL_LARGEST_BATCH_ID = -1;
44+
4245
/** The initial sequence number for each index. Gets updated during index backfill. */
4346
public static final int INITIAL_SEQUENCE_NUMBER = 0;
4447

4548
/** The state of an index that has not yet been backfilled. */
4649
public static IndexState INITIAL_STATE =
47-
IndexState.create(INITIAL_SEQUENCE_NUMBER, SnapshotVersion.NONE, DocumentKey.empty());
50+
IndexState.create(INITIAL_SEQUENCE_NUMBER, IndexOffset.NONE);
4851

4952
/** Compares indexes by collection group and segments. Ignores update time and index ID. */
5053
public static final Comparator<FieldIndex> SEMANTIC_COMPARATOR =
@@ -100,8 +103,11 @@ public static IndexState create(long sequenceNumber, IndexOffset offset) {
100103
}
101104

102105
public static IndexState create(
103-
long sequenceNumber, SnapshotVersion readTime, DocumentKey documentKey) {
104-
return create(sequenceNumber, IndexOffset.create(readTime, documentKey));
106+
long sequenceNumber,
107+
SnapshotVersion readTime,
108+
DocumentKey documentKey,
109+
int largestBatchId) {
110+
return create(sequenceNumber, IndexOffset.create(readTime, documentKey, largestBatchId));
105111
}
106112

107113
/**
@@ -116,17 +122,20 @@ public static IndexState create(
116122
/** Stores the latest read time and document that were processed for an index. */
117123
@AutoValue
118124
public abstract static class IndexOffset implements Comparable<IndexOffset> {
125+
public static final IndexOffset NONE =
126+
create(SnapshotVersion.NONE, DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
127+
119128
public static final Comparator<MutableDocument> DOCUMENT_COMPARATOR =
120129
(l, r) -> IndexOffset.fromDocument(l).compareTo(IndexOffset.fromDocument(r));
121130

122-
public static final IndexOffset NONE = create(SnapshotVersion.NONE, DocumentKey.empty());
123-
124131
/**
125132
* Creates an offset that matches all documents with a read time higher than {@code readTime} or
126-
* with a key higher than {@code documentKey} for equal read times.
133+
* with a key higher than {@code documentKey} for equal read times. The largest batch ID is used
134+
* as a final tie breaker.
127135
*/
128-
public static IndexOffset create(SnapshotVersion readTime, DocumentKey documentKey) {
129-
return new AutoValue_FieldIndex_IndexOffset(readTime, documentKey);
136+
public static IndexOffset create(
137+
SnapshotVersion readTime, DocumentKey key, int largestBatchId) {
138+
return new AutoValue_FieldIndex_IndexOffset(readTime, key, largestBatchId);
130139
}
131140

132141
/**
@@ -145,12 +154,12 @@ public static IndexOffset create(SnapshotVersion readTime) {
145154
successorNanos == 1e9
146155
? new Timestamp(successorSeconds + 1, 0)
147156
: new Timestamp(successorSeconds, successorNanos));
148-
return new AutoValue_FieldIndex_IndexOffset(successor, DocumentKey.empty());
157+
return create(successor, DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
149158
}
150159

151160
/** Creates a new offset based on the provided document. */
152161
public static IndexOffset fromDocument(Document document) {
153-
return new AutoValue_FieldIndex_IndexOffset(document.getReadTime(), document.getKey());
162+
return create(document.getReadTime(), document.getKey(), INITIAL_LARGEST_BATCH_ID);
154163
}
155164

156165
/**
@@ -164,10 +173,17 @@ public static IndexOffset fromDocument(Document document) {
164173
*/
165174
public abstract DocumentKey getDocumentKey();
166175

176+
/*
177+
* Returns the largest mutation batch id that's been processed by Firestore.
178+
*/
179+
public abstract int getLargestBatchId();
180+
167181
public int compareTo(IndexOffset other) {
168182
int cmp = getReadTime().compareTo(other.getReadTime());
169183
if (cmp != 0) return cmp;
170-
return getDocumentKey().compareTo(other.getDocumentKey());
184+
cmp = getDocumentKey().compareTo(other.getDocumentKey());
185+
if (cmp != 0) return cmp;
186+
return Integer.compare(getLargestBatchId(), other.getLargestBatchId());
171187
}
172188
}
173189

firebase-firestore/src/test/java/com/google/firebase/firestore/local/IndexBackfillerTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ private void addFieldIndex(String collectionGroup, String fieldName, SnapshotVer
289289
fieldIndex(
290290
collectionGroup,
291291
FieldIndex.UNKNOWN_ID,
292-
FieldIndex.IndexState.create(0, version, DocumentKey.empty()),
292+
FieldIndex.IndexState.create(0, version, DocumentKey.empty(), -1),
293293
fieldName,
294294
FieldIndex.Segment.Kind.ASCENDING);
295295
indexManager.addFieldIndex(fieldIndex);
@@ -300,7 +300,7 @@ private void addFieldIndex(String collectionGroup, String fieldName, long sequen
300300
fieldIndex(
301301
collectionGroup,
302302
FieldIndex.UNKNOWN_ID,
303-
FieldIndex.IndexState.create(sequenceNumber, SnapshotVersion.NONE, DocumentKey.empty()),
303+
FieldIndex.IndexState.create(sequenceNumber, IndexOffset.NONE),
304304
fieldName,
305305
FieldIndex.Segment.Kind.ASCENDING);
306306
indexManager.addFieldIndex(fieldIndex);

firebase-firestore/src/test/java/com/google/firebase/firestore/local/RemoteDocumentCacheTestCase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public void testGetAllFromSinceReadTimeAndDocumentKey() {
232232

233233
ResourcePath collection = path("b");
234234
Map<DocumentKey, MutableDocument> results =
235-
remoteDocumentCache.getAll(collection, IndexOffset.create(version(11), key("b/b")));
235+
remoteDocumentCache.getAll(collection, IndexOffset.create(version(11), key("b/b"), -1));
236236
assertThat(results.values()).containsExactly(doc("b/c", 3, DOC_DATA), doc("b/d", 4, DOC_DATA));
237237
}
238238

firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteIndexManagerTest.java

+10-21
Original file line numberDiff line numberDiff line change
@@ -582,19 +582,18 @@ public void testAdvancedQueries() {
582582
}
583583

584584
@Test
585-
public void testUpdateTime() {
586-
indexManager.addFieldIndex(
587-
fieldIndex(
588-
"coll1",
589-
1,
590-
IndexState.create(-1, version(20), DocumentKey.empty()),
591-
"value",
592-
Kind.ASCENDING));
585+
public void testPersistsIndexOffset() {
586+
indexManager.addFieldIndex(fieldIndex("coll1", "value", Kind.ASCENDING));
587+
IndexOffset offset = IndexOffset.create(version(20), key("coll/doc"), 42);
588+
indexManager.updateCollectionGroup("coll1", offset);
589+
590+
indexManager = persistence.getIndexManager(User.UNAUTHENTICATED);
591+
indexManager.start();
593592

594593
Collection<FieldIndex> indexes = indexManager.getFieldIndexes("coll1");
595594
assertEquals(indexes.size(), 1);
596595
FieldIndex index = indexes.iterator().next();
597-
assertEquals(index.getIndexState().getOffset().getReadTime(), version(20));
596+
assertEquals(offset, index.getIndexState().getOffset());
598597
}
599598

600599
@Test
@@ -638,19 +637,9 @@ public void testGetFieldIndexes() {
638637
@Test
639638
public void testDeleteFieldIndexRemovesEntryFromCollectionGroup() {
640639
indexManager.addFieldIndex(
641-
fieldIndex(
642-
"coll1",
643-
1,
644-
IndexState.create(1, version(30), DocumentKey.empty()),
645-
"value",
646-
Kind.ASCENDING));
640+
fieldIndex("coll1", 1, IndexState.create(1, IndexOffset.NONE), "value", Kind.ASCENDING));
647641
indexManager.addFieldIndex(
648-
fieldIndex(
649-
"coll2",
650-
2,
651-
IndexState.create(2, version(0), DocumentKey.empty()),
652-
"value",
653-
Kind.CONTAINS));
642+
fieldIndex("coll2", 2, IndexState.create(2, IndexOffset.NONE), "value", Kind.CONTAINS));
654643
String collectionGroup = indexManager.getNextCollectionGroupToUpdate();
655644
assertEquals("coll1", collectionGroup);
656645

firebase-firestore/src/test/java/com/google/firebase/firestore/model/FieldIndexTest.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void comparatorIgnoresIndexState() {
5454
FieldIndex indexOriginal = fieldIndex("collA", 1, FieldIndex.INITIAL_STATE);
5555
FieldIndex indexSame = fieldIndex("collA", 1, FieldIndex.INITIAL_STATE);
5656
FieldIndex indexDifferent =
57-
fieldIndex("collA", 1, IndexState.create(1, version(2), DocumentKey.empty()));
57+
fieldIndex("collA", 1, IndexState.create(1, version(2), DocumentKey.empty(), -1));
5858
assertEquals(0, SEMANTIC_COMPARATOR.compare(indexOriginal, indexSame));
5959
assertEquals(0, SEMANTIC_COMPARATOR.compare(indexOriginal, indexDifferent));
6060
}
@@ -94,10 +94,10 @@ public void comparatorIncludesSegmentsLength() {
9494

9595
@Test
9696
public void indexOffsetComparator() {
97-
IndexOffset docAOffset = IndexOffset.create(version(1), key("foo/a"));
98-
IndexOffset docBOffset = IndexOffset.create(version(1), key("foo/b"));
97+
IndexOffset docAOffset = IndexOffset.create(version(1), key("foo/a"), -1);
98+
IndexOffset docBOffset = IndexOffset.create(version(1), key("foo/b"), -1);
9999
IndexOffset version1Offset = IndexOffset.create(version(1));
100-
IndexOffset docCOffset = IndexOffset.create(version(2), key("foo/c"));
100+
IndexOffset docCOffset = IndexOffset.create(version(2), key("foo/c"), -1);
101101
IndexOffset version2Offset = IndexOffset.create(version(2));
102102

103103
assertEquals(-1, docAOffset.compareTo(docBOffset));
@@ -110,7 +110,7 @@ public void indexOffsetComparator() {
110110
@Test
111111
public void indexOffsetAdvancesSeconds() {
112112
IndexOffset actualSuccessor = IndexOffset.create(version(1, (int) 1e9 - 1));
113-
IndexOffset expectedSuccessor = IndexOffset.create(version(2, 0), DocumentKey.empty());
113+
IndexOffset expectedSuccessor = IndexOffset.create(version(2, 0), DocumentKey.empty(), -1);
114114
assertEquals(expectedSuccessor, actualSuccessor);
115115
}
116116
}

0 commit comments

Comments
 (0)