Skip to content

Add ability to delete indexes #3118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 12, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ Task<Void> configureIndices(String json) {
throw new IllegalArgumentException("Failed to parse index configuration", e);
}

return client.configureIndices(parsedIndices);
return client.configureFieldIndexes(parsedIndices);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ public Task<Query> getNamedQuery(String queryName) {
return completionSource.getTask();
}

public Task<Void> configureIndices(List<FieldIndex> fieldIndices) {
public Task<Void> configureFieldIndexes(List<FieldIndex> fieldIndices) {
verifyNotTerminated();
return asyncQueue.enqueue(() -> localStore.configureIndices(fieldIndices));
return asyncQueue.enqueue(() -> localStore.configureFieldIndexes(fieldIndices));
}

public void removeSnapshotsInSyncListener(EventListener<Void> listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public interface IndexManager {
*/
void addFieldIndex(FieldIndex index);

/** Removes the given field index and deletes all index values. */
void deleteFieldIndex(FieldIndex index);

/**
* Returns a list of field indexes that correspond to the specified collection group.
*
Expand All @@ -71,6 +74,9 @@ public interface IndexManager {
*/
Collection<FieldIndex> getFieldIndexes(String collectionGroup);

/** Returns all configured field indexes. */
Collection<FieldIndex> getFieldIndexes();

/**
* Returns an index that can be used to serve the provided target. Returns {@code null} if no
* index is configured.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package com.google.firebase.firestore.local;

import static com.google.firebase.firestore.util.Assert.hardAssert;
import static com.google.firebase.firestore.util.Util.diffCollections;
import static java.util.Arrays.asList;

import android.util.SparseArray;
Expand Down Expand Up @@ -49,6 +50,7 @@
import com.google.firebase.firestore.util.Logger;
import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -778,14 +780,21 @@ public void saveNamedQuery(NamedQuery namedQuery, ImmutableSortedSet<DocumentKey
"Get named query", () -> bundleCache.getNamedQuery(queryName));
}

public void configureIndices(List<FieldIndex> fieldIndices) {
@VisibleForTesting
Collection<FieldIndex> getFieldIndexes() {
return persistence.runTransaction("Get indexes", () -> indexManager.getFieldIndexes());
}

public void configureFieldIndexes(List<FieldIndex> newFieldIndexes) {
persistence.runTransaction(
"Configure indices",
"Configure indexes",
() -> {
// TODO(indexing): Disable no longer active indices
for (FieldIndex fieldIndex : fieldIndices) {
indexManager.addFieldIndex(fieldIndex);
}
diffCollections(
indexManager.getFieldIndexes(),
newFieldIndexes,
FieldIndex.SEMANTIC_COMPARATOR,
indexManager::addFieldIndex,
indexManager::deleteFieldIndex);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public void addFieldIndex(FieldIndex index) {
// Field indices are not supported with memory persistence.
}

@Override
public void deleteFieldIndex(FieldIndex index) {
// Field indices are not supported with memory persistence.
}

@Nullable
@Override
public FieldIndex getFieldIndex(Target target) {
Expand All @@ -86,6 +91,12 @@ public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
return Collections.emptyList();
}

@Override
public Collection<FieldIndex> getFieldIndexes() {
// Field indices are not supported with memory persistence.
return Collections.emptyList();
}

@Override
public void updateIndexEntries(Collection<Document> documents) {
// Field indices are not supported with memory persistence.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ public void addFieldIndex(FieldIndex index) {
memoizeIndex(index);
}

@Override
public void deleteFieldIndex(FieldIndex index) {
db.execute("DELETE FROM index_configuration WHERE index_id = ?", index.getIndexId());
db.execute("DELETE FROM index_entries WHERE index_id = ?", index.getIndexId());

Map<Integer, FieldIndex> collectionIndices = memoizedIndexes.get(index.getCollectionGroup());
if (collectionIndices != null) {
collectionIndices.remove(index.getIndexId());
}
}

private void updateFieldIndex(FieldIndex index) {
db.execute(
"REPLACE INTO index_configuration ("
Expand Down Expand Up @@ -252,6 +263,15 @@ public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
return indexes == null ? Collections.emptyList() : indexes.values();
}

@Override
public Collection<FieldIndex> getFieldIndexes() {
List<FieldIndex> allIndices = new ArrayList<>();
for (Map<Integer, FieldIndex> indices : memoizedIndexes.values()) {
allIndices.addAll(indices.values());
}
return allIndices;
}

/** Stores the index in the memoized indexes table. */
private void memoizeIndex(FieldIndex fieldIndex) {
Map<Integer, FieldIndex> existingIndexes = memoizedIndexes.get(fieldIndex.getCollectionGroup());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
Expand All @@ -32,9 +34,24 @@
*/
public final class FieldIndex {

/** Compares indexes by collection group and segments. Ignores update time and index ID. */
public static final Comparator<FieldIndex> SEMANTIC_COMPARATOR =
(left, right) -> {
int cmp = left.collectionGroup.compareTo(right.collectionGroup);
if (cmp != 0) return cmp;

Iterator<Segment> leftIt = left.segments.iterator();
Iterator<Segment> rightIt = right.segments.iterator();
while (leftIt.hasNext() && rightIt.hasNext()) {
cmp = leftIt.next().compareTo(rightIt.next());
if (cmp != 0) return cmp;
}
return Boolean.compare(leftIt.hasNext(), rightIt.hasNext());
};

/** An index component consisting of field path and index type. */
@AutoValue
public abstract static class Segment {
public abstract static class Segment implements Comparable<Segment> {
/** The type of the index, e.g. for which type of query it can be used. */
public enum Kind {
/** Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries. */
Expand All @@ -55,6 +72,13 @@ public enum Kind {
public String toString() {
return String.format("Segment{fieldPath=%s, kind=%s}", getFieldPath(), getKind());
}

@Override
public int compareTo(Segment other) {
int cmp = getFieldPath().compareTo(other.getFieldPath());
if (cmp != 0) return cmp;
return getKind().compareTo(other.getKind());
}
}

private final String collectionGroup;
Expand Down Expand Up @@ -153,6 +177,7 @@ public boolean equals(Object o) {

FieldIndex fieldIndex = (FieldIndex) o;

if (indexId != fieldIndex.indexId) return false;
if (!segments.equals(fieldIndex.segments)) return false;
if (!updateTime.equals(fieldIndex.updateTime)) return false;
return collectionGroup.equals(fieldIndex.collectionGroup);
Expand All @@ -161,6 +186,7 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
int result = collectionGroup.hashCode();
result = 31 * result + indexId;
result = 31 * result + segments.hashCode();
result = 31 * result + updateTime.hashCode();
return result;
Expand All @@ -169,7 +195,7 @@ public int hashCode() {
@Override
public String toString() {
return String.format(
"FieldIndex{collectionGroup='%s', segments=%s, updateTime=%s}",
collectionGroup, segments, updateTime);
"FieldIndex{indexId=%s, collectionGroup='%s', segments=%s, updateTime=%s}",
indexId, collectionGroup, segments, updateTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
import io.grpc.StatusRuntimeException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

Expand Down Expand Up @@ -97,18 +99,8 @@ public static int compareMixed(double doubleValue, long longValue) {
return NumberComparisonHelper.firestoreCompareDoubleWithLong(doubleValue, longValue);
}

@SuppressWarnings("unchecked")
private static final Comparator COMPARABLE_COMPARATOR =
new Comparator<Comparable<?>>() {
@Override
public int compare(Comparable left, Comparable right) {
return left.compareTo(right);
}
};

@SuppressWarnings("unchecked")
public static <T extends Comparable<T>> Comparator<T> comparator() {
return COMPARABLE_COMPARATOR;
return Comparable::compareTo;
}

public static FirebaseFirestoreException exceptionFromStatus(Status error) {
Expand Down Expand Up @@ -247,4 +239,95 @@ public static StringBuilder repeatSequence(
}
return sb;
}

/**
* Compares two collections for equality using the provided comparator. The method computes the
* intersection and invokes `onAdd` for every element that is in `after` but not `before`.
* `onRemove` is invoked for every element in `before` but missing from `after`.
*
* <p>The method creates a copy of both `before` and `after` and runs in O(n log n), where n is
* the size of the two lists.
*
* @param before The elements that exist in the original set.
* @param after The elements to diff against the original set.
* @param comparator The comparator to use to define equality between elements.
* @param onAdd A function to invoke for every element that is part of `after` but not `before`.
* @param onRemove A function to invoke for every element that is part of `before` but not
* `after`.
*/
public static <T> void diffCollections(
Collection<T> before,
Collection<T> after,
Comparator<T> comparator,
Consumer<T> onAdd,
Consumer<T> onRemove) {
List<T> beforeEntries = new ArrayList<>(before);
Collections.sort(beforeEntries, comparator);
Iterator<T> beforeIt = beforeEntries.iterator();
@Nullable T beforeValue = advanceIterator(beforeIt);

List<T> afterEntries = new ArrayList<>(after);
Collections.sort(afterEntries, comparator);
Iterator<T> afterIt = afterEntries.iterator();
@Nullable T afterValue = advanceIterator(afterIt);

// Walk through the two sets at the same time, using the ordering defined by `comparator`.
while (beforeValue != null || afterValue != null) {
boolean added = false;
boolean removed = false;

if (beforeValue != null && afterValue != null) {
int cmp = comparator.compare(beforeValue, afterValue);
if (cmp < 0) {
// The element was removed if the next element in our ordered walkthrough is only in
// `before`.
removed = true;
} else if (cmp > 0) {
// The element was added if the next element in our ordered walkthrough is only in
// `after`.
added = true;
}
} else if (beforeValue != null) {
removed = true;
} else {
added = true;
}

if (added) {
onAdd.accept(afterValue);
afterValue = advanceIterator(afterIt);
} else if (removed) {
onRemove.accept(beforeValue);
beforeValue = advanceIterator(beforeIt);
} else {
beforeValue = advanceIterator(beforeIt);
afterValue = advanceIterator(afterIt);
}
}
}

/**
* Compares two collections for equality using their natural ordering. The method computes the
* intersection and invokes `onAdd` for every element that is in `after` but not `before`.
* `onRemove` is invoked for every element in `before` but missing from `after`.
*
* <p>The method creates a copy of both `before` and `after` and runs in O(n log n), where n is
* the size of the two lists.
*
* @param before The elements that exist in the original set.
* @param after The elements to diff against the original set.
* @param onAdd A function to invoke for every element that is part of `after` but not `before`.
* @param onRemove A function to invoke for every element that is part of `before` but not
* `after`.
*/
public static <T extends Comparable<T>> void diffCollections(
Collection<T> before, Collection<T> after, Consumer<T> onAdd, Consumer<T> onRemove) {
diffCollections(before, after, Comparable::compareTo, onAdd, onRemove);
}

/** Returns the next element from the iterator or `null` if none available. */
@Nullable
private static <T> T advanceIterator(Iterator<T> it) {
return it.hasNext() ? it.next() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ public void testBackfillWritesOldestDocumentFirst() {
}

@Test
@Ignore("Flaky")
// TODO(indexing): This test is flaky. Fix.
public void testBackfillUpdatesCollectionGroups() {
addFieldIndex("coll1", "foo");
addFieldIndex("coll2", "foo");
Expand Down
Loading