Skip to content

Commit 486abaf

Browse files
Update document index entries (#3132)
1 parent e3f12a5 commit 486abaf

File tree

9 files changed

+235
-153
lines changed

9 files changed

+235
-153
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/index/IndexEntry.java

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,43 @@
1414

1515
package com.google.firebase.firestore.index;
1616

17-
/**
18-
* Represents an index entry saved by the SDK in the local storage. Temporary placeholder, since
19-
* we'll probably serialize the indexValue right away rather than store it.
20-
*/
21-
// TODO(indexing)
22-
public class IndexEntry {
23-
private final int indexId;
24-
private final byte[] arrayValue;
25-
private final byte[] directionalValue;
26-
private final String uid;
27-
private final String documentName;
28-
29-
public IndexEntry(
30-
int indexId, byte[] arrayValue, byte[] directionalValue, String uid, String documentName) {
31-
this.indexId = indexId;
32-
this.arrayValue = arrayValue;
33-
this.directionalValue = directionalValue;
34-
this.uid = uid;
35-
this.documentName = documentName;
36-
}
17+
import static com.google.firebase.firestore.util.Util.compareByteArrays;
18+
import static com.google.firebase.firestore.util.Util.nullSafeCompare;
3719

38-
public int getIndexId() {
39-
return indexId;
40-
}
20+
import com.google.auto.value.AutoValue;
21+
import com.google.firebase.firestore.model.DocumentKey;
22+
import com.google.firebase.firestore.util.Util;
4123

42-
public byte[] getArrayValue() {
43-
return arrayValue;
44-
}
24+
/** Represents an index entry saved by the SDK in its local storage. */
25+
@AutoValue
26+
public abstract class IndexEntry implements Comparable<IndexEntry> {
4527

46-
public byte[] getDirectionalValue() {
47-
return directionalValue;
28+
public static IndexEntry create(
29+
int indexId, DocumentKey documentKey, byte[] arrayValue, byte[] directionalValue) {
30+
return new AutoValue_IndexEntry(indexId, documentKey, arrayValue, directionalValue);
4831
}
4932

50-
public String getUid() {
51-
return uid;
52-
}
33+
public abstract int getIndexId();
34+
35+
public abstract DocumentKey getDocumentKey();
36+
37+
@SuppressWarnings("mutable")
38+
public abstract byte[] getArrayValue();
39+
40+
@SuppressWarnings("mutable")
41+
public abstract byte[] getDirectionalValue();
42+
43+
@Override
44+
public int compareTo(IndexEntry other) {
45+
int cmp = Integer.compare(getIndexId(), other.getIndexId());
46+
if (cmp != 0) return cmp;
47+
48+
cmp = getDocumentKey().compareTo(other.getDocumentKey());
49+
if (cmp != 0) return cmp;
50+
51+
cmp = compareByteArrays(getDirectionalValue(), other.getDirectionalValue());
52+
if (cmp != 0) return cmp;
5353

54-
public String getDocumentName() {
55-
return documentName;
54+
return nullSafeCompare(getArrayValue(), other.getArrayValue(), Util::compareByteArrays);
5655
}
5756
}

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ public interface IndexManager {
5252
*/
5353
List<ResourcePath> getCollectionParents(String collectionId);
5454

55-
/** Updates the index entries for the given document. */
56-
void handleDocumentChange(@Nullable Document oldDocument, @Nullable Document newDocument);
57-
5855
/**
5956
* Adds a field path index.
6057
*
@@ -100,9 +97,6 @@ public interface IndexManager {
10097
*/
10198
void updateCollectionGroup(String collectionGroup, SnapshotVersion readTime);
10299

103-
/**
104-
* Updates the index entries for the provided documents and corresponding field indexes until the
105-
* cap is reached.
106-
*/
100+
/** Updates the index entries for the provided documents. */
107101
void updateIndexEntries(Collection<Document> documents);
108102
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ public List<ResourcePath> getCollectionParents(String collectionId) {
5050
return collectionParentsIndex.getEntries(collectionId);
5151
}
5252

53-
@Override
54-
public void handleDocumentChange(@Nullable Document oldDocument, @Nullable Document newDocument) {
55-
// Field indices are not supported with memory persistence.
56-
}
57-
5853
@Override
5954
public void addFieldIndex(FieldIndex index) {
6055
// Field indices are not supported with memory persistence.

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

Lines changed: 72 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.firebase.firestore.model.Values.isArray;
1818
import static com.google.firebase.firestore.util.Assert.fail;
1919
import static com.google.firebase.firestore.util.Assert.hardAssert;
20+
import static com.google.firebase.firestore.util.Util.diffCollections;
2021
import static com.google.firebase.firestore.util.Util.repeatSequence;
2122
import static java.lang.Math.max;
2223

@@ -30,6 +31,7 @@
3031
import com.google.firebase.firestore.index.DirectionalIndexByteEncoder;
3132
import com.google.firebase.firestore.index.FirestoreIndexValueWriter;
3233
import com.google.firebase.firestore.index.IndexByteEncoder;
34+
import com.google.firebase.firestore.index.IndexEntry;
3335
import com.google.firebase.firestore.model.Document;
3436
import com.google.firebase.firestore.model.DocumentKey;
3537
import com.google.firebase.firestore.model.FieldIndex;
@@ -52,6 +54,8 @@
5254
import java.util.PriorityQueue;
5355
import java.util.Queue;
5456
import java.util.Set;
57+
import java.util.SortedSet;
58+
import java.util.TreeSet;
5559

5660
/** A persisted implementation of IndexManager. */
5761
final class SQLiteIndexManager implements IndexManager {
@@ -220,11 +224,30 @@ public void updateIndexEntries(Collection<Document> documents) {
220224
for (Document document : documents) {
221225
Collection<FieldIndex> fieldIndexes = getFieldIndexes(document.getKey().getCollectionGroup());
222226
for (FieldIndex fieldIndex : fieldIndexes) {
223-
writeEntries(document, fieldIndex);
227+
SortedSet<IndexEntry> existingEntries =
228+
getExistingIndexEntries(document.getKey(), fieldIndex);
229+
SortedSet<IndexEntry> newEntries = computeIndexEntries(document, fieldIndex);
230+
if (!existingEntries.equals(newEntries)) {
231+
updateEntries(document, existingEntries, newEntries);
232+
}
224233
}
225234
}
226235
}
227236

237+
/**
238+
* Updates the index entries for the provided document by deleting entries that are no longer
239+
* referenced in {@code newEntries} and adding all newly added entries.
240+
*/
241+
private void updateEntries(
242+
Document document, SortedSet<IndexEntry> existingEntries, SortedSet<IndexEntry> newEntries) {
243+
Logger.debug(TAG, "Updating index entries for document '%s'", document.getKey());
244+
diffCollections(
245+
existingEntries,
246+
newEntries,
247+
entry -> addIndexEntry(document, entry),
248+
entry -> deleteIndexEntry(document, entry));
249+
}
250+
228251
@Override
229252
public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
230253
hardAssert(started, "IndexManager not started");
@@ -264,70 +287,74 @@ private void memoizeIndex(FieldIndex fieldIndex) {
264287
Math.max(memoizedMaxSequenceNumber, fieldIndex.getIndexState().getSequenceNumber());
265288
}
266289

267-
/** Persists the index entries for the given document. */
268-
private void writeEntries(Document document, FieldIndex fieldIndex) {
290+
/** Creates the index entries for the given document. */
291+
private SortedSet<IndexEntry> computeIndexEntries(Document document, FieldIndex fieldIndex) {
292+
SortedSet<IndexEntry> result = new TreeSet<>();
293+
269294
@Nullable byte[] directionalValue = encodeDirectionalElements(fieldIndex, document);
270295
if (directionalValue == null) {
271-
return;
296+
return result;
272297
}
273298

274299
@Nullable FieldIndex.Segment arraySegment = fieldIndex.getArraySegment();
275300
if (arraySegment != null) {
276301
Value value = document.getField(arraySegment.getFieldPath());
277-
if (!isArray(value)) {
278-
return;
279-
}
280-
281-
for (Value arrayValue : value.getArrayValue().getValuesList()) {
282-
addSingleEntry(
283-
document, fieldIndex.getIndexId(), encodeSingleElement(arrayValue), directionalValue);
302+
if (isArray(value)) {
303+
for (Value arrayValue : value.getArrayValue().getValuesList()) {
304+
result.add(
305+
IndexEntry.create(
306+
fieldIndex.getIndexId(),
307+
document.getKey(),
308+
encodeSingleElement(arrayValue),
309+
directionalValue));
310+
}
284311
}
285312
} else {
286-
addSingleEntry(document, fieldIndex.getIndexId(), /* arrayValue= */ null, directionalValue);
313+
result.add(
314+
IndexEntry.create(
315+
fieldIndex.getIndexId(), document.getKey(), new byte[] {}, directionalValue));
287316
}
288-
}
289-
290-
@Override
291-
public void handleDocumentChange(@Nullable Document oldDocument, @Nullable Document newDocument) {
292-
hardAssert(started, "IndexManager not started");
293-
hardAssert(oldDocument == null, "Support for updating documents is not yet available");
294-
hardAssert(newDocument != null, "Support for removing documents is not yet available");
295317

296-
DocumentKey documentKey = newDocument.getKey();
297-
Collection<FieldIndex> fieldIndices = getFieldIndexes(documentKey.getCollectionGroup());
298-
addIndexEntry(newDocument, fieldIndices);
299-
}
300-
301-
/**
302-
* Writes index entries for the field indexes that apply to the provided document.
303-
*
304-
* @param document The provided document to index.
305-
* @param fieldIndexes A list of field indexes to apply.
306-
*/
307-
private void addIndexEntry(Document document, Collection<FieldIndex> fieldIndexes) {
308-
for (FieldIndex fieldIndex : fieldIndexes) {
309-
writeEntries(document, fieldIndex);
310-
}
318+
return result;
311319
}
312320

313-
/** Adds a single index entry into the index entries table. */
314-
private void addSingleEntry(
315-
Document document, int indexId, @Nullable Object arrayValue, Object directionalValue) {
316-
if (Logger.isDebugEnabled()) {
317-
Logger.debug(
318-
TAG, "Adding index values for document '%s' to index '%s'", document.getKey(), indexId);
319-
}
320-
321+
private void addIndexEntry(Document document, IndexEntry indexEntry) {
321322
db.execute(
322323
"INSERT INTO index_entries (index_id, uid, array_value, directional_value, document_name) "
323324
+ "VALUES(?, ?, ?, ?, ?)",
324-
indexId,
325+
indexEntry.getIndexId(),
325326
uid,
326-
arrayValue,
327-
directionalValue,
327+
indexEntry.getArrayValue(),
328+
indexEntry.getDirectionalValue(),
328329
document.getKey().toString());
329330
}
330331

332+
private void deleteIndexEntry(Document document, IndexEntry indexEntry) {
333+
db.execute(
334+
"DELETE FROM index_entries WHERE index_id = ? AND uid = ? AND array_value = ? "
335+
+ "AND directional_value = ? AND document_name = ?",
336+
indexEntry.getIndexId(),
337+
uid,
338+
indexEntry.getArrayValue(),
339+
indexEntry.getDirectionalValue(),
340+
document.getKey().toString());
341+
}
342+
343+
private SortedSet<IndexEntry> getExistingIndexEntries(
344+
DocumentKey documentKey, FieldIndex fieldIndex) {
345+
SortedSet<IndexEntry> results = new TreeSet<>();
346+
db.query(
347+
"SELECT array_value, directional_value FROM index_entries "
348+
+ "WHERE index_id = ? AND document_name = ? AND uid = ?")
349+
.binding(fieldIndex.getIndexId(), documentKey.toString(), uid)
350+
.forEach(
351+
row ->
352+
results.add(
353+
IndexEntry.create(
354+
fieldIndex.getIndexId(), documentKey, row.getBlob(0), row.getBlob(1))));
355+
return results;
356+
}
357+
331358
@Override
332359
public Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target target) {
333360
hardAssert(started, "IndexManager not started");

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,23 +377,23 @@ private void createFieldIndex() {
377377
// Per index per user state to track the backfill state for each index
378378
db.execSQL(
379379
"CREATE TABLE index_state ("
380-
+ "uid TEXT, "
381380
+ "index_id INTEGER, "
381+
+ "uid TEXT, "
382382
+ "sequence_number INTEGER, " // Specifies the order of updates
383383
+ "read_time_seconds INTEGER, " // Read time of last processed document
384384
+ "read_time_nanos INTEGER, "
385-
+ "PRIMARY KEY (uid, index_id))");
385+
+ "PRIMARY KEY (index_id, uid))");
386386

387387
// The index entry table stores the encoded entries for all fields.
388388
// The table only has a single primary index. `array_value` should be set for all queries.
389389
db.execSQL(
390390
"CREATE TABLE index_entries ("
391-
+ "uid TEXT, " // user id
392391
+ "index_id INTEGER, " // The index_id of the field index creating this entry
392+
+ "uid TEXT, "
393393
+ "array_value BLOB, " // index values for ArrayContains/ArrayContainsAny
394394
+ "directional_value BLOB, " // index values for equality and inequalities
395395
+ "document_name TEXT, "
396-
+ "PRIMARY KEY (uid, index_id, array_value, directional_value, document_name))");
396+
+ "PRIMARY KEY (index_id, uid, array_value, directional_value, document_name))");
397397

398398
db.execSQL(
399399
"CREATE INDEX read_time ON remote_documents(read_time_seconds, read_time_nanos)");

0 commit comments

Comments
 (0)