Skip to content

Commit ef4631e

Browse files
committed
Merge remote-tracking branch 'origin/master' into OverlayNeverNull
2 parents c0afc9e + 2cb5492 commit ef4631e

File tree

13 files changed

+355
-158
lines changed

13 files changed

+355
-158
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.annotations;
16+
17+
import java.lang.annotation.ElementType;
18+
import java.lang.annotation.Target;
19+
20+
/**
21+
* Indicates that this object (class, method, etc) is experimental and that both its signature and
22+
* implementation are subject to change. An API marked with this annotation provides no guarantee of
23+
* API stability or backward-compatibility.
24+
*/
25+
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
26+
public @interface PreviewApi {}

firebase-firestore/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ Android changes are not released automatically. Ensure that changes are released
22
by opting into a release at
33
[go/firebase-android-release](http:go/firebase-android-release) (Googlers only).
44

5+
# Unreleased
6+
- [feature] Added experimental support for indexed query execution. Indexes can
7+
be enabled by invoking `FirebaseFirestore.setIndexConfiguration()` with the
8+
JSON index definition exported by the Firestore CLI. Queries against the
9+
cache are executed using an index once the asynchronous operation to generate
10+
the index entries completes.
11+
512
# 24.0.2
613
- [fixed] Fixed an issue of long grpc reconnection period, when App moves to
714
foreground after staying in background for a while.
815
- [fixed] Fixed an AppCheck issue that caused Firestore listeners to stop
916
working and receive a "Permission Denied" error. This issue only occurred for
1017
AppCheck users that set their expiration time to under an hour.
11-
- [fixed] Fixed a potential problem during Firestore's shutdown that prevented
18+
- [fixed] Fixed a potential problem during Firestore's shutdown that prevented
1219
the shutdown from proceeding if a network connection was opened right before.
1320
- [changed] Queries are now send to the backend before the SDK starts local
1421
processing, which reduces overall Query latency.

firebase-firestore/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ package com.google.firebase.firestore {
153153
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> runBatch(@NonNull com.google.firebase.firestore.WriteBatch.Function);
154154
method @NonNull public <TResult> com.google.android.gms.tasks.Task<TResult> runTransaction(@NonNull com.google.firebase.firestore.Transaction.Function<TResult>);
155155
method public void setFirestoreSettings(@NonNull com.google.firebase.firestore.FirebaseFirestoreSettings);
156+
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setIndexConfiguration(@NonNull String);
156157
method public static void setLoggingEnabled(boolean);
157158
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> terminate();
158159
method public void useEmulator(@NonNull String, int);

firebase-firestore/firebase-firestore.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ googleJavaFormat {
123123
}
124124

125125
dependencies {
126+
implementation project(':firebase-annotations')
126127
implementation project(':firebase-common')
127128
implementation project(':protolite-well-known-types')
128129
implementation project(':firebase-database-collection')

firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.android.gms.tasks.TaskCompletionSource;
2828
import com.google.android.gms.tasks.Tasks;
2929
import com.google.firebase.FirebaseApp;
30+
import com.google.firebase.annotations.PreviewApi;
3031
import com.google.firebase.appcheck.interop.InternalAppCheckTokenProvider;
3132
import com.google.firebase.auth.internal.InternalAuthProvider;
3233
import com.google.firebase.emulators.EmulatedServiceSettings;
@@ -304,8 +305,9 @@ public FirebaseApp getApp() {
304305
* @return A task that resolves once all indices are successfully configured.
305306
* @throws IllegalArgumentException if the JSON format is invalid
306307
*/
307-
@VisibleForTesting
308-
Task<Void> setIndexConfiguration(String json) {
308+
@PreviewApi
309+
@NonNull
310+
public Task<Void> setIndexConfiguration(@NonNull String json) {
309311
ensureClientConfigured();
310312
Preconditions.checkState(
311313
settings.isPersistenceEnabled(), "Cannot enable indexes when persistence is disabled");

firebase-firestore/src/main/java/com/google/firebase/firestore/core/Target.java

Lines changed: 143 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import static com.google.firebase.firestore.model.Values.max;
1818
import static com.google.firebase.firestore.model.Values.min;
1919

20+
import android.util.Pair;
2021
import androidx.annotation.Nullable;
22+
import com.google.firebase.firestore.core.OrderBy.Direction;
2123
import com.google.firebase.firestore.model.DocumentKey;
2224
import com.google.firebase.firestore.model.FieldIndex;
2325
import com.google.firebase.firestore.model.FieldPath;
@@ -185,65 +187,18 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
185187

186188
// For each segment, retrieve a lower bound if there is a suitable filter or startAt.
187189
for (FieldIndex.Segment segment : fieldIndex.getDirectionalSegments()) {
188-
Value segmentValue = null;
189-
boolean segmentInclusive = true;
190+
Pair<Value, Boolean> segmentBound =
191+
segment.getKind().equals(FieldIndex.Segment.Kind.ASCENDING)
192+
? getAscendingBound(segment, startAt)
193+
: getDescendingBound(segment, startAt);
190194

191-
// Process all filters to find a value for the current field segment
192-
for (FieldFilter fieldFilter : getFieldFiltersForPath(segment.getFieldPath())) {
193-
Value filterValue = null;
194-
boolean filterInclusive = true;
195-
196-
switch (fieldFilter.getOperator()) {
197-
case LESS_THAN:
198-
case LESS_THAN_OR_EQUAL:
199-
filterValue = Values.getLowerBound(fieldFilter.getValue().getValueTypeCase());
200-
break;
201-
case EQUAL:
202-
case IN:
203-
case GREATER_THAN_OR_EQUAL:
204-
filterValue = fieldFilter.getValue();
205-
break;
206-
case GREATER_THAN:
207-
filterValue = fieldFilter.getValue();
208-
filterInclusive = false;
209-
break;
210-
case NOT_EQUAL:
211-
case NOT_IN:
212-
filterValue = Values.MIN_VALUE;
213-
break;
214-
default:
215-
// Remaining filters cannot be used as lower bounds.
216-
}
217-
218-
if (max(segmentValue, filterValue) == filterValue) {
219-
segmentValue = filterValue;
220-
segmentInclusive = filterInclusive;
221-
}
222-
}
223-
224-
// If there is a startAt bound, compare the values against the existing boundary to see
225-
// if we can narrow the scope.
226-
if (startAt != null) {
227-
for (int i = 0; i < orderBys.size(); ++i) {
228-
OrderBy orderBy = this.orderBys.get(i);
229-
if (orderBy.getField().equals(segment.getFieldPath())) {
230-
Value cursorValue = startAt.getPosition().get(i);
231-
if (max(segmentValue, cursorValue) == cursorValue) {
232-
segmentValue = cursorValue;
233-
segmentInclusive = startAt.isInclusive();
234-
}
235-
break;
236-
}
237-
}
238-
}
239-
240-
if (segmentValue == null) {
195+
if (segmentBound.first == null) {
241196
// No lower bound exists
242197
return null;
243198
}
244199

245-
values.add(segmentValue);
246-
inclusive &= segmentInclusive;
200+
values.add(segmentBound.first);
201+
inclusive &= segmentBound.second;
247202
}
248203

249204
return new Bound(values, inclusive);
@@ -259,75 +214,159 @@ public Bound getLowerBound(FieldIndex fieldIndex) {
259214

260215
// For each segment, retrieve an upper bound if there is a suitable filter or endAt.
261216
for (FieldIndex.Segment segment : fieldIndex.getDirectionalSegments()) {
262-
@Nullable Value segmentValue = null;
263-
boolean segmentInclusive = true;
217+
Pair<Value, Boolean> segmentBound =
218+
segment.getKind().equals(FieldIndex.Segment.Kind.ASCENDING)
219+
? getDescendingBound(segment, endAt)
220+
: getAscendingBound(segment, endAt);
264221

265-
// Process all filters to find a value for the current field segment
266-
for (FieldFilter fieldFilter : getFieldFiltersForPath(segment.getFieldPath())) {
267-
Value filterValue = null;
268-
boolean filterInclusive = true;
222+
if (segmentBound.first == null) {
223+
// No upper bound exists
224+
return null;
225+
}
269226

270-
switch (fieldFilter.getOperator()) {
271-
case GREATER_THAN_OR_EQUAL:
272-
case GREATER_THAN:
273-
filterValue = Values.getUpperBound(fieldFilter.getValue().getValueTypeCase());
274-
filterInclusive = false;
275-
break;
276-
case EQUAL:
277-
case IN:
278-
case LESS_THAN_OR_EQUAL:
279-
filterValue = fieldFilter.getValue();
280-
break;
281-
case LESS_THAN:
282-
filterValue = fieldFilter.getValue();
283-
filterInclusive = false;
284-
break;
285-
case NOT_EQUAL:
286-
case NOT_IN:
287-
filterValue = Values.MAX_VALUE;
288-
break;
289-
default:
290-
// Remaining filters cannot be used as upper bounds.
291-
}
227+
values.add(segmentBound.first);
228+
inclusive &= segmentBound.second;
229+
}
292230

293-
if (min(segmentValue, filterValue) == filterValue) {
294-
segmentValue = filterValue;
295-
segmentInclusive = filterInclusive;
296-
}
231+
return new Bound(values, inclusive);
232+
}
233+
234+
/**
235+
* Returns the value for an ascending bound of `segment`.
236+
*
237+
* @param segment The segment to get the value for.
238+
* @param bound A bound to restrict the index range.
239+
* @return a Pair with a nullable Value and a boolean indicating whether the bound is inclusive
240+
*/
241+
private Pair<Value, Boolean> getAscendingBound(
242+
FieldIndex.Segment segment, @Nullable Bound bound) {
243+
Value segmentValue = null;
244+
boolean segmentInclusive = true;
245+
246+
// Process all filters to find a value for the current field segment
247+
for (FieldFilter fieldFilter : getFieldFiltersForPath(segment.getFieldPath())) {
248+
Value filterValue = null;
249+
boolean filterInclusive = true;
250+
251+
switch (fieldFilter.getOperator()) {
252+
case LESS_THAN:
253+
case LESS_THAN_OR_EQUAL:
254+
filterValue = Values.getLowerBound(fieldFilter.getValue().getValueTypeCase());
255+
break;
256+
case EQUAL:
257+
case IN:
258+
case GREATER_THAN_OR_EQUAL:
259+
filterValue = fieldFilter.getValue();
260+
break;
261+
case GREATER_THAN:
262+
filterValue = fieldFilter.getValue();
263+
filterInclusive = false;
264+
break;
265+
case NOT_EQUAL:
266+
case NOT_IN:
267+
filterValue = Values.MIN_VALUE;
268+
break;
269+
default:
270+
// Remaining filters cannot be used as bound.
297271
}
298272

299-
// If there is an endAt bound, compare the values against the existing boundary to see
300-
// if we can narrow the scope.
301-
if (endAt != null) {
302-
for (int i = 0; i < orderBys.size(); ++i) {
303-
OrderBy orderBy = this.orderBys.get(i);
304-
if (orderBy.getField().equals(segment.getFieldPath())) {
305-
Value cursorValue = endAt.getPosition().get(i);
306-
if (min(segmentValue, cursorValue) == cursorValue) {
307-
segmentValue = cursorValue;
308-
segmentInclusive = endAt.isInclusive();
309-
}
310-
break;
273+
if (max(segmentValue, filterValue) == filterValue) {
274+
segmentValue = filterValue;
275+
segmentInclusive = filterInclusive;
276+
}
277+
}
278+
279+
// If there is an additional bound, compare the values against the existing range to see if we
280+
// can narrow the scope.
281+
if (bound != null) {
282+
for (int i = 0; i < orderBys.size(); ++i) {
283+
OrderBy orderBy = this.orderBys.get(i);
284+
if (orderBy.getField().equals(segment.getFieldPath())) {
285+
Value cursorValue = bound.getPosition().get(i);
286+
if (max(segmentValue, cursorValue) == cursorValue) {
287+
segmentValue = cursorValue;
288+
segmentInclusive = bound.isInclusive();
311289
}
312290
}
313291
}
292+
}
314293

315-
if (segmentValue == null) {
316-
// No upper bound exists
317-
return null;
294+
return new Pair<>(segmentValue, segmentInclusive);
295+
}
296+
297+
/**
298+
* Returns the value for a descending bound of `segment`.
299+
*
300+
* @param segment The segment to get the value for.
301+
* @param bound A bound to restrict the index range.
302+
* @return a Pair with a nullable Value and a boolean indicating whether the bound is inclusive
303+
*/
304+
private Pair<Value, Boolean> getDescendingBound(
305+
FieldIndex.Segment segment, @Nullable Bound bound) {
306+
Value segmentValue = null;
307+
boolean segmentInclusive = true;
308+
309+
// Process all filters to find a value for the current field segment
310+
for (FieldFilter fieldFilter : getFieldFiltersForPath(segment.getFieldPath())) {
311+
Value filterValue = null;
312+
boolean filterInclusive = true;
313+
314+
switch (fieldFilter.getOperator()) {
315+
case GREATER_THAN_OR_EQUAL:
316+
case GREATER_THAN:
317+
filterValue = Values.getUpperBound(fieldFilter.getValue().getValueTypeCase());
318+
filterInclusive = false;
319+
break;
320+
case EQUAL:
321+
case IN:
322+
case LESS_THAN_OR_EQUAL:
323+
filterValue = fieldFilter.getValue();
324+
break;
325+
case LESS_THAN:
326+
filterValue = fieldFilter.getValue();
327+
filterInclusive = false;
328+
break;
329+
case NOT_EQUAL:
330+
case NOT_IN:
331+
filterValue = Values.MAX_VALUE;
332+
break;
333+
default:
334+
// Remaining filters cannot be used as bound.
318335
}
319336

320-
values.add(segmentValue);
321-
inclusive &= segmentInclusive;
337+
if (min(segmentValue, filterValue) == filterValue) {
338+
segmentValue = filterValue;
339+
segmentInclusive = filterInclusive;
340+
}
322341
}
323342

324-
return new Bound(values, inclusive);
343+
// If there is an additional bound, compare the values against the existing range to see if we
344+
// can narrow the scope.
345+
if (bound != null) {
346+
for (int i = 0; i < orderBys.size(); ++i) {
347+
OrderBy orderBy = this.orderBys.get(i);
348+
if (orderBy.getField().equals(segment.getFieldPath())) {
349+
Value cursorValue = bound.getPosition().get(i);
350+
if (min(segmentValue, cursorValue) == cursorValue) {
351+
segmentValue = cursorValue;
352+
segmentInclusive = bound.isInclusive();
353+
}
354+
}
355+
}
356+
}
357+
358+
return new Pair<>(segmentValue, segmentInclusive);
325359
}
326360

327361
public List<OrderBy> getOrderBy() {
328362
return this.orderBys;
329363
}
330364

365+
/** Returns the order of the document key component. */
366+
public Direction getKeyOrder() {
367+
return this.orderBys.get(this.orderBys.size() - 1).getDirection();
368+
}
369+
331370
/** Returns a canonical string representing this target. */
332371
public String getCanonicalId() {
333372
if (memoizedCanonicalId != null) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.firebase.firestore.model.ResourcePath;
2525
import java.util.Collection;
2626
import java.util.List;
27-
import java.util.Set;
2827

2928
/**
3029
* Represents a set of indexes that are used to execute queries efficiently.
@@ -95,7 +94,7 @@ public interface IndexManager {
9594
* Returns the documents that match the given target based on the provided index or {@code null}
9695
* if the query cannot be served from an index.
9796
*/
98-
Set<DocumentKey> getDocumentsMatchingTarget(Target target);
97+
List<DocumentKey> getDocumentsMatchingTarget(Target target);
9998

10099
/** Returns the next collection group to update. Returns {@code null} if no group exists. */
101100
@Nullable

0 commit comments

Comments
 (0)