Skip to content

Commit d775f44

Browse files
Update document index entries
1 parent cafe359 commit d775f44

File tree

7 files changed

+202
-111
lines changed

7 files changed

+202
-111
lines changed

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

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,52 @@
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 androidx.annotation.Nullable;
21+
import com.google.auto.value.AutoValue;
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 the 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,
30+
String documentName,
31+
@Nullable String uid,
32+
@Nullable byte[] arrayValue,
33+
byte[] directionalValue) {
34+
return new AutoValue_IndexEntry(indexId, documentName, uid, arrayValue, directionalValue);
4835
}
4936

50-
public String getUid() {
51-
return uid;
52-
}
37+
public abstract int getIndexId();
38+
39+
public abstract String getDocumentName();
40+
41+
public abstract @Nullable String getUid();
42+
43+
@SuppressWarnings("mutable")
44+
public abstract @Nullable byte[] getArrayValue();
45+
46+
@SuppressWarnings("mutable")
47+
public abstract byte[] getDirectionalValue();
48+
49+
@Override
50+
public int compareTo(IndexEntry other) {
51+
int cmp = Integer.compare(getIndexId(), other.getIndexId());
52+
if (cmp != 0) return cmp;
53+
54+
cmp = getDocumentName().compareTo(other.getDocumentName());
55+
if (cmp != 0) return cmp;
56+
57+
cmp = nullSafeCompare(getUid(), other.getUid());
58+
if (cmp != 0) return cmp;
59+
60+
cmp = compareByteArrays(getDirectionalValue(), other.getDirectionalValue());
61+
if (cmp != 0) return cmp;
5362

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

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

Lines changed: 2 additions & 6 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
*
@@ -86,9 +83,8 @@ public interface IndexManager {
8683
String getNextCollectionGroupToUpdate(Timestamp lastUpdateTime);
8784

8885
/**
89-
* Updates the index entries for the provided documents and corresponding field indexes until the
90-
* cap is reached. Updates the field indexes in persistence with the latest read time that was
91-
* processed.
86+
* Updates the index entries for the provided documents and corresponding field indexes. Updates
87+
* the field indexes in persistence with the latest read time that was processed.
9288
*/
9389
void updateIndexEntries(Collection<Document> documents);
9490
}

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: 118 additions & 66 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.advanceIterator;
2021
import static com.google.firebase.firestore.util.Util.repeatSequence;
2122
import static java.lang.Math.max;
2223

@@ -31,6 +32,7 @@
3132
import com.google.firebase.firestore.index.DirectionalIndexByteEncoder;
3233
import com.google.firebase.firestore.index.FirestoreIndexValueWriter;
3334
import com.google.firebase.firestore.index.IndexByteEncoder;
35+
import com.google.firebase.firestore.index.IndexEntry;
3436
import com.google.firebase.firestore.model.Document;
3537
import com.google.firebase.firestore.model.DocumentKey;
3638
import com.google.firebase.firestore.model.FieldIndex;
@@ -51,9 +53,12 @@
5153
import java.util.List;
5254
import java.util.Map;
5355
import java.util.Set;
56+
import java.util.SortedSet;
57+
import java.util.TreeSet;
5458

5559
/** A persisted implementation of IndexManager. */
5660
final class SQLiteIndexManager implements IndexManager {
61+
5762
private static final String TAG = SQLiteIndexManager.class.getSimpleName();
5863

5964
/**
@@ -224,10 +229,12 @@ public void updateIndexEntries(Collection<Document> documents) {
224229
for (Document document : documents) {
225230
Collection<FieldIndex> fieldIndexes = getFieldIndexes(document.getKey().getCollectionGroup());
226231
for (FieldIndex fieldIndex : fieldIndexes) {
227-
boolean modified = writeEntries(document, fieldIndex);
228-
if (modified) {
232+
List<IndexEntry> existingEntries = getIndexEntries(document, fieldIndex);
233+
List<IndexEntry> newEntries = computeEntries(document, fieldIndex);
234+
if (!existingEntries.equals(newEntries)) {
229235
// TODO(indexing): This would be much simpler with a sequence counter since we would
230236
// always update the index to the next sequence value.
237+
updateEntries(document, existingEntries, newEntries);
231238
FieldIndex latestIndex =
232239
updatedFieldIndexes.get(fieldIndex.getIndexId()) != null
233240
? updatedFieldIndexes.get(fieldIndex.getIndexId())
@@ -245,6 +252,50 @@ public void updateIndexEntries(Collection<Document> documents) {
245252
}
246253
}
247254

255+
private void updateEntries(
256+
Document document, List<IndexEntry> existingEntries, List<IndexEntry> updatedEntries) {
257+
if (Logger.isDebugEnabled()) {
258+
Logger.debug(TAG, "Updating index entries for document '%s'", document.getKey());
259+
}
260+
261+
SortedSet<IndexEntry> existingIndexes = new TreeSet<>(existingEntries);
262+
Iterator<IndexEntry> existingIt = existingIndexes.iterator();
263+
@Nullable IndexEntry existingValue = advanceIterator(existingIt);
264+
265+
List<IndexEntry> updatedIndexes = new ArrayList<>(updatedEntries);
266+
Iterator<IndexEntry> updatedIt = updatedIndexes.iterator();
267+
@Nullable IndexEntry updatedValue = advanceIterator(updatedIt);
268+
269+
while (existingValue != null || updatedValue != null) {
270+
boolean deleted = false;
271+
boolean updated = false;
272+
273+
if (existingValue != null && updatedValue != null) {
274+
int cmp = existingValue.compareTo(updatedValue);
275+
if (cmp < 0) {
276+
deleted = true;
277+
} else if (cmp > 0) {
278+
updated = true;
279+
}
280+
} else if (existingValue != null) {
281+
deleted = true;
282+
} else {
283+
updated = true;
284+
}
285+
286+
if (deleted) {
287+
deleteIndexEntry(document, existingValue);
288+
existingValue = advanceIterator(existingIt);
289+
} else if (updated) {
290+
addIndexEntry(document, updatedValue);
291+
updatedValue = advanceIterator(updatedIt);
292+
} else {
293+
existingValue = advanceIterator(existingIt);
294+
updatedValue = advanceIterator(updatedIt);
295+
}
296+
}
297+
}
298+
248299
@Override
249300
public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
250301
hardAssert(started, "IndexManager not started");
@@ -276,84 +327,85 @@ private FieldIndex getPostUpdateIndex(FieldIndex baseIndex, SnapshotVersion newR
276327
}
277328
}
278329

279-
/**
280-
* If applicable, writes index entries for the given document. Returns whether any index entry was
281-
* written.
282-
*/
283-
private boolean writeEntries(Document document, FieldIndex fieldIndex) {
284-
@Nullable byte[] directionalValue = encodeDirectionalElements(fieldIndex, document);
285-
if (directionalValue == null) {
286-
return false;
287-
}
288-
289-
@Nullable FieldIndex.Segment arraySegment = fieldIndex.getArraySegment();
290-
if (arraySegment != null) {
291-
Value value = document.getField(arraySegment.getFieldPath());
292-
if (!isArray(value)) {
293-
return false;
294-
}
295-
296-
for (Value arrayValue : value.getArrayValue().getValuesList()) {
297-
addSingleEntry(
298-
document, fieldIndex.getIndexId(), encodeSingleElement(arrayValue), directionalValue);
299-
}
300-
return true;
301-
} else {
302-
addSingleEntry(document, fieldIndex.getIndexId(), /* arrayValue= */ null, directionalValue);
303-
return true;
304-
}
305-
}
306-
307-
@Override
308-
public void handleDocumentChange(@Nullable Document oldDocument, @Nullable Document newDocument) {
309-
hardAssert(started, "IndexManager not started");
310-
hardAssert(oldDocument == null, "Support for updating documents is not yet available");
311-
hardAssert(newDocument != null, "Support for removing documents is not yet available");
312-
313-
DocumentKey documentKey = newDocument.getKey();
314-
Collection<FieldIndex> fieldIndices = getFieldIndexes(documentKey.getCollectionGroup());
315-
addIndexEntry(newDocument, fieldIndices);
316-
}
330+
/** Creates a list of index entries for the given document. */
331+
private List<IndexEntry> computeEntries(Document document, FieldIndex fieldIndex) {
332+
List<IndexEntry> indexEntries = new ArrayList<>();
317333

318-
/**
319-
* Writes index entries for the field indexes that apply to the provided document.
320-
*
321-
* @param document The provided document to index.
322-
* @param fieldIndexes A list of field indexes to apply.
323-
*/
324-
private void addIndexEntry(Document document, Collection<FieldIndex> fieldIndexes) {
325-
List<FieldIndex> updatedIndexes = new ArrayList<>();
326-
327-
for (FieldIndex fieldIndex : fieldIndexes) {
328-
boolean modified = writeEntries(document, fieldIndex);
329-
if (modified) {
330-
updatedIndexes.add(getPostUpdateIndex(fieldIndex, document.getVersion()));
334+
@Nullable byte[] directionalValue = encodeDirectionalElements(fieldIndex, document);
335+
if (directionalValue != null) {
336+
@Nullable FieldIndex.Segment arraySegment = fieldIndex.getArraySegment();
337+
if (arraySegment != null) {
338+
Value value = document.getField(arraySegment.getFieldPath());
339+
if (isArray(value)) {
340+
for (Value arrayValue : value.getArrayValue().getValuesList()) {
341+
indexEntries.add(
342+
IndexEntry.create(
343+
fieldIndex.getIndexId(),
344+
document.getKey().getName(),
345+
document.hasLocalMutations() ? user.getUid() : null,
346+
directionalValue,
347+
encodeSingleElement(arrayValue)));
348+
}
349+
}
350+
} else {
351+
indexEntries.add(
352+
IndexEntry.create(
353+
fieldIndex.getIndexId(),
354+
document.getKey().getName(),
355+
document.hasLocalMutations() ? user.getUid() : null,
356+
directionalValue,
357+
/* arrayValue= */ null));
331358
}
332359
}
333360

334-
for (FieldIndex updatedIndex : updatedIndexes) {
335-
updateFieldIndex(updatedIndex);
336-
}
361+
return indexEntries;
337362
}
338363

339364
/** Adds a single index entry into the index entries table. */
340-
private void addSingleEntry(
341-
Document document, int indexId, @Nullable Object arrayValue, Object directionalValue) {
342-
if (Logger.isDebugEnabled()) {
343-
Logger.debug(
344-
TAG, "Adding index values for document '%s' to index '%s'", document.getKey(), indexId);
345-
}
346-
365+
private void addIndexEntry(Document document, IndexEntry indexEntry) {
347366
db.execute(
348367
"INSERT INTO index_entries (index_id, uid, array_value, directional_value, document_name) "
349368
+ "VALUES(?, ?, ?, ?, ?)",
350-
indexId,
369+
indexEntry.getIndexId(),
351370
document.hasLocalMutations() ? user.getUid() : null,
352-
arrayValue,
353-
directionalValue,
371+
indexEntry.getArrayValue(),
372+
indexEntry.getDirectionalValue(),
354373
document.getKey().toString());
355374
}
356375

376+
/** Removes a single index entry from the index entries table. */
377+
private void deleteIndexEntry(Document document, IndexEntry indexEntry) {
378+
if (document.hasLocalMutations()) {
379+
db.execute(
380+
"DELETE FROM index_entries WHERE index_id = ? AND uid = ? AND document_name = ?",
381+
indexEntry.getIndexId(),
382+
document.hasLocalMutations(),
383+
document.getKey().toString());
384+
} else {
385+
db.execute(
386+
"DELETE FROM index_entries WHERE index_id = ? AND uid IS NULL AND document_name = ?",
387+
indexEntry.getIndexId(),
388+
document.getKey().toString());
389+
}
390+
}
391+
392+
private List<IndexEntry> getIndexEntries(Document document, FieldIndex fieldIndex) {
393+
List<IndexEntry> results = new ArrayList<>();
394+
db.query(
395+
"SELECT user_id, directional_value, array_value, FROM index_entries WHERE index_id = ? AND document_name = ? AND (user_id IS NULL OR user_id = ?)")
396+
.binding(fieldIndex.getIndexId(), document.getKey().getName(), user.getUid())
397+
.forEach(
398+
row ->
399+
results.add(
400+
IndexEntry.create(
401+
fieldIndex.getIndexId(),
402+
document.getKey().getName(),
403+
row.isNull(1) ? null : row.getString(1),
404+
row.getBlob(2),
405+
row.isNull(3) ? null : row.getBlob(3))));
406+
return results;
407+
}
408+
357409
@Override
358410
public Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target target) {
359411
hardAssert(started, "IndexManager not started");

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public ResourcePath getPath() {
108108
return path;
109109
}
110110

111+
/** Returns the document name. */
112+
public String getName() {
113+
return path.getLastSegment();
114+
}
115+
111116
/** Returns the collection group (i.e. the name of the parent collection) for this key. */
112117
public String getCollectionGroup() {
113118
return path.getSegment(path.length() - 2);

0 commit comments

Comments
 (0)