Skip to content

Make FieldIndex an AutoValue #3147

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 6 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.google.firebase.firestore.model.FieldIndex;
import com.google.firebase.firestore.model.FieldPath;
import com.google.firebase.firestore.model.ResourcePath;
import com.google.firebase.firestore.model.SnapshotVersion;
import com.google.firebase.firestore.remote.FirestoreChannel;
import com.google.firebase.firestore.remote.GrpcMetadataProvider;
import com.google.firebase.firestore.util.AsyncQueue;
Expand Down Expand Up @@ -322,21 +323,24 @@ Task<Void> configureIndices(String json) {
JSONArray indices = jsonObject.getJSONArray("indexes");
for (int i = 0; i < indices.length(); ++i) {
JSONObject definition = indices.getJSONObject(i);
FieldIndex fieldIndex = new FieldIndex(definition.getString("collectionGroup"));
String collectionGroup = definition.getString("collectionGroup");
List<FieldIndex.Segment> segments = new ArrayList<>();

JSONArray fields = definition.optJSONArray("fields");
for (int f = 0; fields != null && f < fields.length(); ++f) {
JSONObject field = fields.getJSONObject(f);
FieldPath fieldPath = FieldPath.fromServerFormat(field.getString("fieldPath"));
if ("CONTAINS".equals(field.optString("arrayConfig"))) {
fieldIndex = fieldIndex.withAddedField(fieldPath, FieldIndex.Segment.Kind.CONTAINS);
segments.add(FieldIndex.Segment.create(fieldPath, FieldIndex.Segment.Kind.CONTAINS));
} else if ("ASCENDING".equals(field.optString("order"))) {
fieldIndex = fieldIndex.withAddedField(fieldPath, FieldIndex.Segment.Kind.ASCENDING);
segments.add(FieldIndex.Segment.create(fieldPath, FieldIndex.Segment.Kind.ASCENDING));
} else {
fieldIndex = fieldIndex.withAddedField(fieldPath, FieldIndex.Segment.Kind.DESCENDING);
segments.add(
FieldIndex.Segment.create(fieldPath, FieldIndex.Segment.Kind.DESCENDING));
}
parsedIndices.add(fieldIndex);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an actual bug. I changed the test to catch this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully follow. What was the original bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It adds an the same index multiple times if it has more than one field.

}

parsedIndices.add(FieldIndex.create(-1, collectionGroup, segments, SnapshotVersion.NONE));
}
}
} catch (JSONException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,13 @@ public BundledQuery decodeBundledQuery(com.google.firestore.bundle.BundledQuery
return new BundledQuery(target, limitType);
}

public Index encodeFieldIndex(FieldIndex fieldIndex) {
public Index encodeFieldIndexSegments(List<FieldIndex.Segment> segments) {
Index.Builder index = Index.newBuilder();
// The Mobile SDKs treat all indices as collection group indices, as we run all collection group
// queries against each collection separately.
index.setQueryScope(Index.QueryScope.COLLECTION_GROUP);

for (int i = 0; i < fieldIndex.segmentCount(); ++i) {
FieldIndex.Segment segment = fieldIndex.getSegment(i);
for (FieldIndex.Segment segment : segments) {
Index.IndexField.Builder indexField = Index.IndexField.newBuilder();
indexField.setFieldPath(segment.getFieldPath().canonicalString());
if (segment.getKind() == FieldIndex.Segment.Kind.CONTAINS) {
Expand All @@ -314,9 +313,8 @@ public Index encodeFieldIndex(FieldIndex fieldIndex) {
return index.build();
}

public FieldIndex decodeFieldIndex(
String collectionGroup, int indexId, Index index, int updateSeconds, int updateNanos) {
FieldIndex fieldIndex = new FieldIndex(collectionGroup, indexId);
public List<FieldIndex.Segment> decodeFieldIndexSegments(Index index) {
List<FieldIndex.Segment> result = new ArrayList<>();
for (Index.IndexField field : index.getFieldsList()) {
FieldPath fieldPath = FieldPath.fromServerFormat(field.getFieldPath());
FieldIndex.Segment.Kind kind =
Expand All @@ -325,11 +323,9 @@ public FieldIndex decodeFieldIndex(
: (field.getOrder().equals(Index.IndexField.Order.ASCENDING)
? FieldIndex.Segment.Kind.ASCENDING
: FieldIndex.Segment.Kind.DESCENDING);
fieldIndex = fieldIndex.withAddedField(fieldPath, kind);
result.add(FieldIndex.Segment.create(fieldPath, kind));
}
fieldIndex =
fieldIndex.withUpdateTime(new SnapshotVersion(new Timestamp(updateSeconds, updateNanos)));
return fieldIndex;
return result;
}

public Mutation decodeMutation(Write mutation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ public void start() {
.forEach(
row -> {
try {
int indexId = row.getInt(0);
String collectionGroup = row.getString(1);
List<FieldIndex.Segment> segments =
serializer.decodeFieldIndexSegments(Index.parseFrom(row.getBlob(2)));
SnapshotVersion updateTime =
new SnapshotVersion(new Timestamp(row.getLong(3), row.getInt(4)));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seconds are stored in SQLite as INTEGER, does it auto-convert to long?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INTEGER means non-floating number of any storage size for SQLite: https://www.sqlite.org/datatype3.html


FieldIndex fieldIndex =
serializer.decodeFieldIndex(
row.getString(1),
row.getInt(0),
Index.parseFrom(row.getBlob(2)),
row.getInt(3),
row.getInt(4));
FieldIndex.create(indexId, collectionGroup, segments, updateTime);
memoizeIndex(fieldIndex);
} catch (InvalidProtocolBufferException e) {
throw fail("Failed to decode index: " + e);
Expand Down Expand Up @@ -146,7 +148,9 @@ public void addFieldIndex(FieldIndex index) {
hardAssert(started, "IndexManager not started");

int nextIndexId = memoizedMaxId + 1;
index = index.withIndexId(nextIndexId);
index =
FieldIndex.create(
nextIndexId, index.getCollectionGroup(), index.getSegments(), index.getUpdateTime());

db.execute(
"INSERT INTO index_configuration ("
Expand All @@ -157,7 +161,7 @@ public void addFieldIndex(FieldIndex index) {
+ "update_time_nanos) VALUES(?, ?, ?, ?, ?)",
nextIndexId,
index.getCollectionGroup(),
encodeFieldIndex(index),
encodeSegments(index),
index.getUpdateTime().getTimestamp().getSeconds(),
index.getUpdateTime().getTimestamp().getNanoseconds());

Expand Down Expand Up @@ -188,7 +192,7 @@ private void updateFieldIndex(FieldIndex index) {
+ "update_time_nanos) VALUES(?, ?, ?, ?, ?)",
index.getIndexId(),
index.getCollectionGroup(),
encodeFieldIndex(index),
encodeSegments(index),
index.getUpdateTime().getTimestamp().getSeconds(),
index.getUpdateTime().getTimestamp().getNanoseconds());

Expand Down Expand Up @@ -292,7 +296,11 @@ private FieldIndex getPostUpdateIndex(FieldIndex baseIndex, SnapshotVersion newR
if (baseIndex.getUpdateTime().compareTo(newReadTime) > 0) {
return baseIndex;
} else {
return baseIndex.withUpdateTime(newReadTime);
return FieldIndex.create(
baseIndex.getIndexId(),
baseIndex.getCollectionGroup(),
baseIndex.getSegments(),
newReadTime);
}
}

Expand Down Expand Up @@ -545,7 +553,7 @@ public FieldIndex getFieldIndex(Target target) {

// Return the index with the most number of segments
return Collections.max(
matchingIndexes, (l, r) -> Integer.compare(l.segmentCount(), r.segmentCount()));
matchingIndexes, (l, r) -> Integer.compare(l.getSegments().size(), r.getSegments().size()));
}

/**
Expand Down Expand Up @@ -651,8 +659,8 @@ private boolean isInFilter(Target target, FieldPath fieldPath) {
return false;
}

private byte[] encodeFieldIndex(FieldIndex fieldIndex) {
return serializer.encodeFieldIndex(fieldIndex).toByteArray();
private byte[] encodeSegments(FieldIndex fieldIndex) {
return serializer.encodeFieldIndexSegments(fieldIndex.getSegments()).toByteArray();
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@
* group-scoped indices. Every index can be used for both single collection and collection group
* queries.
*/
public final class FieldIndex {
@AutoValue
public abstract 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);
int cmp = left.getCollectionGroup().compareTo(right.getCollectionGroup());
if (cmp != 0) return cmp;

Iterator<Segment> leftIt = left.segments.iterator();
Iterator<Segment> rightIt = right.segments.iterator();
Iterator<Segment> leftIt = left.getSegments().iterator();
Iterator<Segment> rightIt = right.getSegments().iterator();
while (leftIt.hasNext() && rightIt.hasNext()) {
cmp = leftIt.next().compareTo(rightIt.next());
if (cmp != 0) return cmp;
Expand All @@ -62,17 +63,16 @@ public enum Kind {
CONTAINS
}

public static Segment create(FieldPath fieldPath, Kind kind) {
return new AutoValue_FieldIndex_Segment(fieldPath, kind);
}

/** The field path of the component. */
public abstract FieldPath getFieldPath();

/** The indexes sorting order. */
public abstract Kind getKind();

@Override
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());
Expand All @@ -81,121 +81,45 @@ public int compareTo(Segment other) {
}
}

private final String collectionGroup;
private final int indexId;
private final List<Segment> segments;
private final SnapshotVersion updateTime;

public FieldIndex(String collectionGroup, int indexId) {
this.collectionGroup = collectionGroup;
this.segments = new ArrayList<>();
this.indexId = indexId;
this.updateTime = SnapshotVersion.NONE;
}

public FieldIndex(String collectionId) {
this(collectionId, -1);
}

FieldIndex(
String collectionGroup, int indexId, List<Segment> segments, SnapshotVersion updateTime) {
this.collectionGroup = collectionGroup;
this.segments = segments;
this.indexId = indexId;
this.updateTime = updateTime;
}

/** The collection ID this index applies to. */
public String getCollectionGroup() {
return collectionGroup;
public static FieldIndex create(
int indexId, String collectionGroup, List<Segment> segments, SnapshotVersion updateTime) {
return new AutoValue_FieldIndex(indexId, collectionGroup, segments, updateTime);
}

/**
* The index ID. Returns -1 if the index ID is not available (e.g. the index has not yet been
* persisted).
*/
public int getIndexId() {
return indexId;
}
public abstract int getIndexId();

public Segment getSegment(int index) {
return segments.get(index);
}
/** The collection ID this index applies to. */
public abstract String getCollectionGroup();

public int segmentCount() {
return segments.size();
}
/** Returns all field segments for this index. */
public abstract List<Segment> getSegments();

/**
* Returns the latest read time version that has been indexed by Firestore for this field index.
*/
public SnapshotVersion getUpdateTime() {
return updateTime;
}
/** Returns when this index was last updated. */
public abstract SnapshotVersion getUpdateTime();

/** Returns all directional (ascending/descending) segments for this index. */
public List<Segment> getDirectionalSegments() {
List<Segment> filteredSegments = new ArrayList<>();
for (Segment segment : segments) {
for (Segment segment : getSegments()) {
if (!segment.getKind().equals(Segment.Kind.CONTAINS)) {
filteredSegments.add(segment);
}
}
return filteredSegments;
}

/** Returns the ArrayContains/ArrayContainsAny segment for this index. */
public @Nullable Segment getArraySegment() {
for (Segment segment : segments) {
for (Segment segment : getSegments()) {
if (segment.getKind().equals(Segment.Kind.CONTAINS)) {
// Firestore queries can only have a single ArrayContains/ArrayContainsAny statements.
return segment;
}
}
return null;
}

/** Returns a new field index with additional index segment. */
public FieldIndex withAddedField(FieldPath fieldPath, Segment.Kind kind) {
List<Segment> newSegments = new ArrayList<>(segments);
newSegments.add(new AutoValue_FieldIndex_Segment(fieldPath, kind));
return new FieldIndex(collectionGroup, indexId, newSegments, updateTime);
}

/** Returns a new field index with the updated version. */
public FieldIndex withUpdateTime(SnapshotVersion updateTime) {
return new FieldIndex(collectionGroup, indexId, segments, updateTime);
}

/** Returns a new field index with the provided index id. */
public FieldIndex withIndexId(int indexId) {
return new FieldIndex(collectionGroup, indexId, segments, updateTime);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

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);
}

@Override
public int hashCode() {
int result = collectionGroup.hashCode();
result = 31 * result + indexId;
result = 31 * result + segments.hashCode();
result = 31 * result + updateTime.hashCode();
return result;
}

@Override
public String toString() {
return String.format(
"FieldIndex{indexId=%s, collectionGroup='%s', segments=%s, updateTime=%s}",
indexId, collectionGroup, segments, updateTime);
}
}
Loading