Skip to content

Commit bcef03c

Browse files
Add getNext()
1 parent 1c7dd64 commit bcef03c

File tree

16 files changed

+290
-82
lines changed

16 files changed

+290
-82
lines changed

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

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,15 @@
1919
import androidx.annotation.Nullable;
2020
import androidx.annotation.VisibleForTesting;
2121
import com.google.firebase.database.collection.ImmutableSortedMap;
22-
import com.google.firebase.firestore.core.Query;
2322
import com.google.firebase.firestore.model.Document;
2423
import com.google.firebase.firestore.model.DocumentKey;
2524
import com.google.firebase.firestore.model.FieldIndex;
2625
import com.google.firebase.firestore.model.FieldIndex.IndexOffset;
27-
import com.google.firebase.firestore.model.ResourcePath;
2826
import com.google.firebase.firestore.util.AsyncQueue;
2927
import com.google.firebase.firestore.util.Logger;
30-
import java.util.ArrayList;
3128
import java.util.Collection;
32-
import java.util.Collections;
3329
import java.util.HashSet;
34-
import java.util.List;
30+
import java.util.Iterator;
3531
import java.util.Map;
3632
import java.util.Set;
3733
import java.util.concurrent.TimeUnit;
@@ -138,22 +134,18 @@ private int writeIndexEntries(LocalDocumentsView localDocumentsView) {
138134
/** Writes entries for the fetched field indexes. */
139135
private int writeEntriesForCollectionGroup(
140136
LocalDocumentsView localDocumentsView, String collectionGroup, int entriesRemainingUnderCap) {
141-
Query query = new Query(ResourcePath.EMPTY, collectionGroup);
137+
// TODO(indexing): Support mutation batch Ids when sorting and writing indexes.
142138

143139
// Use the earliest offset of all field indexes to query the local cache.
144140
IndexOffset existingOffset = getExistingOffset(indexManager.getFieldIndexes(collectionGroup));
145-
146-
// TODO(indexing): Use limit queries to only fetch the required number of entries.
147-
// TODO(indexing): Support mutation batch Ids when sorting and writing indexes.
148141
ImmutableSortedMap<DocumentKey, Document> documents =
149-
localDocumentsView.getDocumentsMatchingQuery(query, existingOffset);
150-
151-
List<Document> oldestDocuments = getOldestDocuments(documents, entriesRemainingUnderCap);
152-
indexManager.updateIndexEntries(oldestDocuments);
142+
localDocumentsView.getDocuments(collectionGroup, existingOffset, entriesRemainingUnderCap);
143+
indexManager.updateIndexEntries(documents);
153144

154-
IndexOffset newOffset = getNewOffset(oldestDocuments, existingOffset);
145+
IndexOffset newOffset = getNewOffset(documents, existingOffset);
155146
indexManager.updateCollectionGroup(collectionGroup, newOffset);
156-
return oldestDocuments.size();
147+
148+
return documents.size();
157149
}
158150

159151
/** Returns the lowest offset for the provided index group. */
@@ -168,37 +160,22 @@ private IndexOffset getExistingOffset(Collection<FieldIndex> fieldIndexes) {
168160
return lowestOffset == null ? IndexOffset.NONE : lowestOffset;
169161
}
170162

171-
/**
172-
* Returns the offset for the index based on the newly indexed documents.
173-
*
174-
* @param documents a list of documents sorted by read time and key (ascending)
175-
* @param currentOffset the current offset of the index group
176-
*/
177-
private IndexOffset getNewOffset(List<Document> documents, IndexOffset currentOffset) {
178-
IndexOffset latestOffset =
179-
documents.isEmpty()
180-
? IndexOffset.create(remoteDocumentCache.getLatestReadTime())
181-
: IndexOffset.create(
182-
documents.get(documents.size() - 1).getReadTime(),
183-
documents.get(documents.size() - 1).getKey());
184-
// Make sure the index does not go back in time
185-
latestOffset = latestOffset.compareTo(currentOffset) > 0 ? latestOffset : currentOffset;
186-
return latestOffset;
187-
}
188-
189-
/** Returns up to {@code count} documents sorted by read time and key. */
190-
private List<Document> getOldestDocuments(
191-
ImmutableSortedMap<DocumentKey, Document> documents, int count) {
192-
List<Document> oldestDocuments = new ArrayList<>();
193-
for (Map.Entry<DocumentKey, Document> entry : documents) {
194-
oldestDocuments.add(entry.getValue());
163+
/** Returns the offset for the index based on the newly indexed documents. */
164+
private IndexOffset getNewOffset(
165+
ImmutableSortedMap<DocumentKey, Document> documents, IndexOffset currentOffset) {
166+
if (documents.isEmpty()) {
167+
return IndexOffset.create(remoteDocumentCache.getLatestReadTime());
168+
} else {
169+
IndexOffset latestOffset = currentOffset;
170+
Iterator<Map.Entry<DocumentKey, Document>> it = documents.iterator();
171+
while (it.hasNext()) {
172+
IndexOffset newOffset = IndexOffset.fromDocument(it.next().getValue());
173+
if (newOffset.compareTo(latestOffset) > 0) {
174+
latestOffset = newOffset;
175+
}
176+
}
177+
return latestOffset;
195178
}
196-
Collections.sort(
197-
oldestDocuments,
198-
(l, r) ->
199-
IndexOffset.create(l.getReadTime(), l.getKey())
200-
.compareTo(IndexOffset.create(r.getReadTime(), r.getKey())));
201-
return oldestDocuments.subList(0, Math.min(count, oldestDocuments.size()));
202179
}
203180

204181
@VisibleForTesting

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.firestore.local;
1616

1717
import androidx.annotation.Nullable;
18+
import com.google.firebase.database.collection.ImmutableSortedMap;
1819
import com.google.firebase.firestore.core.Target;
1920
import com.google.firebase.firestore.model.Document;
2021
import com.google.firebase.firestore.model.DocumentKey;
@@ -97,5 +98,5 @@ public interface IndexManager {
9798
void updateCollectionGroup(String collectionGroup, FieldIndex.IndexOffset offset);
9899

99100
/** Updates the index entries for the provided documents. */
100-
void updateIndexEntries(Collection<Document> documents);
101+
void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents);
101102
}

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,15 @@ void recalculateAndSaveOverlays(Set<DocumentKey> documentKeys) {
237237
recalculateAndSaveOverlays(docs);
238238
}
239239

240-
// TODO: The Querying implementation here should move 100% to the query engines.
241-
// Instead, we should just provide a getCollectionDocuments() method here that return all the
242-
// documents in a given collection so that query engine can do that and then filter in
243-
// memory.
240+
/**
241+
* Gets the local view of the next {@code count} documents based on their read time.
242+
*/
243+
ImmutableSortedMap<DocumentKey, Document> getDocuments(
244+
String collectionGroup, IndexOffset offset, int count) {
245+
Map<DocumentKey, MutableDocument> docs =
246+
remoteDocumentCache.getAll(collectionGroup, offset, count);
247+
return getLocalViewOfDocuments(docs, new HashSet<>());
248+
}
244249

245250
/**
246251
* Performs a query against the local view of all documents.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static com.google.firebase.firestore.util.Assert.hardAssert;
1717

1818
import androidx.annotation.Nullable;
19+
import com.google.firebase.database.collection.ImmutableSortedMap;
1920
import com.google.firebase.firestore.core.Target;
2021
import com.google.firebase.firestore.model.Document;
2122
import com.google.firebase.firestore.model.DocumentKey;
@@ -98,7 +99,7 @@ public Collection<FieldIndex> getFieldIndexes() {
9899
}
99100

100101
@Override
101-
public void updateIndexEntries(Collection<Document> documents) {
102+
public void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents) {
102103
// Field indices are not supported with memory persistence.
103104
}
104105

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
import com.google.firebase.firestore.model.SnapshotVersion;
2828
import java.util.HashMap;
2929
import java.util.Iterator;
30+
import java.util.List;
3031
import java.util.Map;
32+
import java.util.Set;
33+
import java.util.TreeSet;
3134

3235
/** In-memory cache of remote documents. */
3336
final class MemoryRemoteDocumentCache implements RemoteDocumentCache {
@@ -81,6 +84,38 @@ public Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> keys) {
8184
return result;
8285
}
8386

87+
@Override
88+
public Map<DocumentKey, MutableDocument> getAll(
89+
String collectionGroup, IndexOffset offset, int count) {
90+
List<ResourcePath> collectionParents = indexManager.getCollectionParents(collectionGroup);
91+
92+
Set<MutableDocument> allDocuments =
93+
new TreeSet<>((l, r) -> IndexOffset.fromDocument(l).compareTo(IndexOffset.fromDocument(r)));
94+
for (ResourcePath collectionParent : collectionParents) {
95+
ResourcePath documentParent = collectionParent.append(collectionGroup);
96+
Iterator<Map.Entry<DocumentKey, MutableDocument>> iterator =
97+
docs.iteratorFrom(DocumentKey.fromPath(documentParent.append("")));
98+
while (iterator.hasNext()) {
99+
Map.Entry<DocumentKey, MutableDocument> entry = iterator.next();
100+
DocumentKey key = entry.getKey();
101+
if (!documentParent.isPrefixOf(key.getPath())) {
102+
break;
103+
}
104+
if (IndexOffset.fromDocument(entry.getValue()).compareTo(offset) > 0) {
105+
allDocuments.add(entry.getValue());
106+
}
107+
}
108+
}
109+
110+
Map<DocumentKey, MutableDocument> matchingDocuments = new HashMap<>();
111+
Iterator<MutableDocument> it = allDocuments.iterator();
112+
while (it.hasNext() && matchingDocuments.size() < count) {
113+
MutableDocument document = it.next();
114+
matchingDocuments.put(document.getKey(), document);
115+
}
116+
return matchingDocuments;
117+
}
118+
84119
@Override
85120
public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQuery(
86121
Query query, IndexOffset offset) {
@@ -108,7 +143,7 @@ public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQ
108143
continue;
109144
}
110145

111-
if (IndexOffset.create(doc.getReadTime(), doc.getKey()).compareTo(offset) <= 0) {
146+
if (IndexOffset.fromDocument(doc).compareTo(offset) <= 0) {
112147
continue;
113148
}
114149

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import com.google.firebase.database.collection.ImmutableSortedMap;
1818
import com.google.firebase.firestore.core.Query;
1919
import com.google.firebase.firestore.model.DocumentKey;
20-
import com.google.firebase.firestore.model.FieldIndex;
20+
import com.google.firebase.firestore.model.FieldIndex.IndexOffset;
2121
import com.google.firebase.firestore.model.MutableDocument;
2222
import com.google.firebase.firestore.model.SnapshotVersion;
2323
import java.util.Map;
@@ -65,6 +65,20 @@ interface RemoteDocumentCache {
6565
*/
6666
Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> documentKeys);
6767

68+
/**
69+
* Looks up the next {@code count} documents for a collection group based on the provided offset.
70+
* The ordering is based on the document's read time and key.
71+
*
72+
* <p>This method may return more results than requested if a collection group scan scans more
73+
* than 125 collections.
74+
*
75+
* @param collectionGroup The collection group to scan.
76+
* @param offset The offset to start the scan at.
77+
* @param count The number of results to return.
78+
* @return A map with next set of documents.
79+
*/
80+
Map<DocumentKey, MutableDocument> getAll(String collectionGroup, IndexOffset offset, int count);
81+
6882
/**
6983
* Executes a query against the cached Document entries
7084
*
@@ -78,7 +92,7 @@ interface RemoteDocumentCache {
7892
* @return The set of matching documents.
7993
*/
8094
ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQuery(
81-
Query query, FieldIndex.IndexOffset offset);
95+
Query query, IndexOffset offset);
8296

8397
/** Returns the latest read time of any document in the cache. */
8498
SnapshotVersion getLatestReadTime();

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import androidx.annotation.Nullable;
2525
import com.google.firebase.Timestamp;
26+
import com.google.firebase.database.collection.ImmutableSortedMap;
27+
import com.google.firebase.database.collection.LLRBNode;
2628
import com.google.firebase.firestore.auth.User;
2729
import com.google.firebase.firestore.core.Bound;
2830
import com.google.firebase.firestore.core.FieldFilter;
@@ -222,19 +224,22 @@ public void deleteFieldIndex(FieldIndex index) {
222224
}
223225

224226
@Override
225-
public void updateIndexEntries(Collection<Document> documents) {
227+
public void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents) {
226228
hardAssert(started, "IndexManager not started");
227-
for (Document document : documents) {
228-
Collection<FieldIndex> fieldIndexes = getFieldIndexes(document.getKey().getCollectionGroup());
229-
for (FieldIndex fieldIndex : fieldIndexes) {
230-
SortedSet<IndexEntry> existingEntries =
231-
getExistingIndexEntries(document.getKey(), fieldIndex);
232-
SortedSet<IndexEntry> newEntries = computeIndexEntries(document, fieldIndex);
233-
if (!existingEntries.equals(newEntries)) {
234-
updateEntries(document, existingEntries, newEntries);
235-
}
236-
}
237-
}
229+
documents.inOrderTraversal(
230+
new LLRBNode.NodeVisitor<DocumentKey, Document>() {
231+
@Override
232+
public void visitEntry(DocumentKey key, Document document) {
233+
Collection<FieldIndex> fieldIndexes = getFieldIndexes(key.getCollectionGroup());
234+
for (FieldIndex fieldIndex : fieldIndexes) {
235+
SortedSet<IndexEntry> existingEntries = getExistingIndexEntries(key, fieldIndex);
236+
SortedSet<IndexEntry> newEntries = computeIndexEntries(document, fieldIndex);
237+
if (!existingEntries.equals(newEntries)) {
238+
updateEntries(document, existingEntries, newEntries);
239+
}
240+
}
241+
}
242+
});
238243
}
239244

240245
/**

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
* helper routines that make dealing with SQLite much more pleasant.
5555
*/
5656
public final class SQLitePersistence extends Persistence {
57+
/**
58+
* The maximum number of bind args for a single statement. Set to 900 instead of 999 for safety.
59+
*/
60+
public static final int MAX_ARGS = 900;
5761

5862
/**
5963
* Creates the database name that is used to identify the database to be used with a Firestore

0 commit comments

Comments
 (0)