Skip to content

Commit f2e4bd0

Browse files
wu-huidconeybe
andauthored
Public Count (#4130)
* Public Count * Disable prod testing * Long to long * Api.txt * Backfill changelog * Add PR * Fix assertEquals error * Re-write API javadocs for COUNT API (#4143) Co-authored-by: Denver Coneybeare <[email protected]>
1 parent 1d344e8 commit f2e4bd0

File tree

7 files changed

+144
-73
lines changed

7 files changed

+144
-73
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@ by opting into a release at
33
[go/firebase-android-release](http:go/firebase-android-release) (Googlers only).
44

55
# Unreleased
6+
- [feature] Added `Query.count()`, which fetches the number of documents in the
7+
result set without actually downloading the documents (#4130).
8+
9+
# 24.3.1
10+
- [changed] Updated dependency of `io.grpc.*` to its latest version (v1.48.1).
11+
12+
# 24.3.0
13+
- [changed] Updated dependency of `play-services-basement` to its latest version (v18.1.0).
14+
15+
# 24.2.2
616
- [fixed] Fixed an issue `waitForPendingWrites()` could lead to NullPointerException.
717

18+
# 24.2.1
19+
- [changed] Internal refactor and test improvements.
20+
821
# 24.2.0
922
- [feature] Added `TransactionOptions` to control how many times a transaction
1023
will retry commits before failing.

firebase-firestore/api.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ package com.google.firebase {
1919

2020
package com.google.firebase.firestore {
2121

22+
public class AggregateQuery {
23+
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.firestore.AggregateQuerySnapshot> get(@NonNull com.google.firebase.firestore.AggregateSource);
24+
method @NonNull public com.google.firebase.firestore.Query getQuery();
25+
}
26+
27+
public class AggregateQuerySnapshot {
28+
method public long getCount();
29+
method @NonNull public com.google.firebase.firestore.AggregateQuery getQuery();
30+
}
31+
32+
public enum AggregateSource {
33+
enum_constant public static final com.google.firebase.firestore.AggregateSource SERVER;
34+
}
35+
2236
public class Blob implements java.lang.Comparable<com.google.firebase.firestore.Blob> {
2337
method public int compareTo(@NonNull com.google.firebase.firestore.Blob);
2438
method @NonNull public static com.google.firebase.firestore.Blob fromBytes(@NonNull byte[]);
@@ -290,6 +304,7 @@ package com.google.firebase.firestore {
290304
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener<com.google.firebase.firestore.QuerySnapshot>);
291305
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener<com.google.firebase.firestore.QuerySnapshot>);
292306
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener<com.google.firebase.firestore.QuerySnapshot>);
307+
method @NonNull public com.google.firebase.firestore.AggregateQuery count();
293308
method @NonNull public com.google.firebase.firestore.Query endAt(@NonNull com.google.firebase.firestore.DocumentSnapshot);
294309
method @NonNull public com.google.firebase.firestore.Query endAt(java.lang.Object...);
295310
method @NonNull public com.google.firebase.firestore.Query endBefore(@NonNull com.google.firebase.firestore.DocumentSnapshot);

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,8 @@ public void testCanRunCount() {
8888
"b", map("k", "b"),
8989
"c", map("k", "c")));
9090

91-
AggregateQuerySnapshot snapshot =
92-
waitFor(collection.count().get(AggregateSource.SERVER_DIRECT));
93-
assertEquals(Long.valueOf(3), snapshot.getCount());
91+
AggregateQuerySnapshot snapshot = waitFor(collection.count().get(AggregateSource.SERVER));
92+
assertEquals(3L, snapshot.getCount());
9493
}
9594

9695
@Test
@@ -103,8 +102,8 @@ public void testCanRunCountWithFilters() {
103102
"c", map("k", "c")));
104103

105104
AggregateQuerySnapshot snapshot =
106-
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT));
107-
assertEquals(Long.valueOf(1), snapshot.getCount());
105+
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER));
106+
assertEquals(1L, snapshot.getCount());
108107
}
109108

110109
@Test
@@ -118,9 +117,9 @@ public void testCanRunCountWithOrderBy() {
118117
"d", map("absent", "d")));
119118

120119
AggregateQuerySnapshot snapshot =
121-
waitFor(collection.orderBy("k").count().get(AggregateSource.SERVER_DIRECT));
120+
waitFor(collection.orderBy("k").count().get(AggregateSource.SERVER));
122121
// "d" is filtered out because it is ordered by "k".
123-
assertEquals(Long.valueOf(3), snapshot.getCount());
122+
assertEquals(3L, snapshot.getCount());
124123
}
125124

126125
@Test
@@ -132,7 +131,7 @@ public void testTerminateDoesNotCrashWithFlyingCountQuery() {
132131
"b", map("k", "b"),
133132
"c", map("k", "c")));
134133

135-
collection.orderBy("k").count().get(AggregateSource.SERVER_DIRECT);
134+
collection.orderBy("k").count().get(AggregateSource.SERVER);
136135
waitFor(collection.firestore.terminate());
137136
}
138137

@@ -146,15 +145,15 @@ public void testSnapshotEquals() {
146145
"c", map("k", "c")));
147146

148147
AggregateQuerySnapshot snapshot1 =
149-
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT));
148+
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER));
150149
AggregateQuerySnapshot snapshot1_same =
151-
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT));
150+
waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER));
152151

153152
AggregateQuerySnapshot snapshot2 =
154-
waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER_DIRECT));
153+
waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER));
155154
waitFor(collection.document("d").set(map("k", "a")));
156155
AggregateQuerySnapshot snapshot2_different =
157-
waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER_DIRECT));
156+
waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER));
158157

159158
assertTrue(snapshot1.equals(snapshot1_same));
160159
assertEquals(snapshot1.hashCode(), snapshot1_same.hashCode());
@@ -196,9 +195,9 @@ public void testCanRunCollectionGroupQuery() {
196195
waitFor(batch.commit());
197196

198197
AggregateQuerySnapshot snapshot =
199-
waitFor(db.collectionGroup(collectionGroup).count().get(AggregateSource.SERVER_DIRECT));
198+
waitFor(db.collectionGroup(collectionGroup).count().get(AggregateSource.SERVER));
200199
assertEquals(
201-
Long.valueOf(5), // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5",
200+
5L, // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5",
202201
snapshot.getCount());
203202
}
204203

@@ -213,40 +212,33 @@ public void testCanRunCountWithFiltersAndLimits() {
213212
"d", map("k", "d")));
214213

215214
AggregateQuerySnapshot snapshot =
216-
waitFor(
217-
collection.whereEqualTo("k", "a").limit(2).count().get(AggregateSource.SERVER_DIRECT));
218-
assertEquals(Long.valueOf(2), snapshot.getCount());
215+
waitFor(collection.whereEqualTo("k", "a").limit(2).count().get(AggregateSource.SERVER));
216+
assertEquals(2L, snapshot.getCount());
219217

220218
snapshot =
221219
waitFor(
222-
collection
223-
.whereEqualTo("k", "a")
224-
.limitToLast(2)
225-
.count()
226-
.get(AggregateSource.SERVER_DIRECT));
227-
assertEquals(Long.valueOf(2), snapshot.getCount());
220+
collection.whereEqualTo("k", "a").limitToLast(2).count().get(AggregateSource.SERVER));
221+
assertEquals(2L, snapshot.getCount());
228222

229223
snapshot =
230224
waitFor(
231225
collection
232226
.whereEqualTo("k", "d")
233227
.limitToLast(1000)
234228
.count()
235-
.get(AggregateSource.SERVER_DIRECT));
236-
assertEquals(Long.valueOf(1), snapshot.getCount());
229+
.get(AggregateSource.SERVER));
230+
assertEquals(1L, snapshot.getCount());
237231
}
238232

239233
@Test
240234
public void testCanRunCountOnNonExistentCollection() {
241235
CollectionReference collection = testFirestore().collection("random-coll");
242236

243-
AggregateQuerySnapshot snapshot =
244-
waitFor(collection.count().get(AggregateSource.SERVER_DIRECT));
245-
assertEquals(Long.valueOf(0), snapshot.getCount());
237+
AggregateQuerySnapshot snapshot = waitFor(collection.count().get(AggregateSource.SERVER));
238+
assertEquals(0L, snapshot.getCount());
246239

247-
snapshot =
248-
waitFor(collection.whereEqualTo("k", 100).count().get(AggregateSource.SERVER_DIRECT));
249-
assertEquals(Long.valueOf(0), snapshot.getCount());
240+
snapshot = waitFor(collection.whereEqualTo("k", 100).count().get(AggregateSource.SERVER));
241+
assertEquals(0L, snapshot.getCount());
250242
}
251243

252244
@Test
@@ -259,14 +251,13 @@ public void testFailWithoutNetwork() {
259251
"c", map("k", "c")));
260252
waitFor(collection.getFirestore().disableNetwork());
261253

262-
Exception e = waitForException(collection.count().get(AggregateSource.SERVER_DIRECT));
254+
Exception e = waitForException(collection.count().get(AggregateSource.SERVER));
263255
assertThat(e, instanceOf(FirebaseFirestoreException.class));
264256
assertEquals(
265257
FirebaseFirestoreException.Code.UNAVAILABLE, ((FirebaseFirestoreException) e).getCode());
266258

267259
waitFor(collection.getFirestore().enableNetwork());
268-
AggregateQuerySnapshot snapshot =
269-
waitFor(collection.count().get(AggregateSource.SERVER_DIRECT));
270-
assertEquals(Long.valueOf(3), snapshot.getCount());
260+
AggregateQuerySnapshot snapshot = waitFor(collection.count().get(AggregateSource.SERVER));
261+
assertEquals(3L, snapshot.getCount());
271262
}
272263
}

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,31 @@
2121
import com.google.firebase.firestore.util.Preconditions;
2222

2323
/**
24-
* A {@code AggregateQuery} computes some aggregation statistics from the result set of a base
25-
* {@link Query}.
24+
* A query that calculates aggregations over an underlying query.
2625
*
2726
* <p><b>Subclassing Note</b>: Cloud Firestore classes are not meant to be subclassed except for use
2827
* in test mocks. Subclassing is not supported in production code and new SDK releases may break
2928
* code that does so.
3029
*/
31-
class AggregateQuery {
32-
// The base query.
30+
public class AggregateQuery {
31+
3332
private final Query query;
3433

3534
AggregateQuery(@NonNull Query query) {
3635
this.query = query;
3736
}
3837

39-
/** Returns the base {@link Query} for this aggregate query. */
38+
/** Returns the query whose aggregations will be calculated by this object. */
4039
@NonNull
4140
public Query getQuery() {
4241
return query;
4342
}
4443

4544
/**
46-
* Executes the aggregate query and returns the results as a {@code AggregateQuerySnapshot}.
45+
* Executes this query.
4746
*
48-
* @param source A value to configure the get behavior.
49-
* @return A Task that will be resolved with the results of the {@code AggregateQuery}.
47+
* @param source The source from which to acquire the aggregate results.
48+
* @return A {@link Task} that will be resolved with the results of the query.
5049
*/
5150
@NonNull
5251
public Task<AggregateQuerySnapshot> get(@NonNull AggregateSource source) {
@@ -70,14 +69,35 @@ public Task<AggregateQuerySnapshot> get(@NonNull AggregateSource source) {
7069
return tcs.getTask();
7170
}
7271

72+
/**
73+
* Compares this object with the given object for equality.
74+
*
75+
* <p>This object is considered "equal" to the other object if and only if all of the following
76+
* conditions are satisfied:
77+
*
78+
* <ol>
79+
* <li>{@code object} is a non-null instance of {@link AggregateQuery}.
80+
* <li>{@code object} performs the same aggregations as this {@link AggregateQuery}.
81+
* <li>The underlying {@link Query} of {@code object} compares equal to that of this object.
82+
* </ol>
83+
*
84+
* @param object The object to compare to this object for equality.
85+
* @return {@code true} if this object is "equal" to the given object, as defined above, or {@code
86+
* false} otherwise.
87+
*/
7388
@Override
74-
public boolean equals(Object o) {
75-
if (this == o) return true;
76-
if (!(o instanceof AggregateQuery)) return false;
77-
AggregateQuery that = (AggregateQuery) o;
78-
return query.equals(that.query);
89+
public boolean equals(Object object) {
90+
if (this == object) return true;
91+
if (!(object instanceof AggregateQuery)) return false;
92+
AggregateQuery other = (AggregateQuery) object;
93+
return query.equals(other.query);
7994
}
8095

96+
/**
97+
* Calculates and returns the hash code for this object.
98+
*
99+
* @return the hash code for this object.
100+
*/
81101
@Override
82102
public int hashCode() {
83103
return query.hashCode();

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

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
import static com.google.firebase.firestore.util.Preconditions.checkNotNull;
1818

1919
import androidx.annotation.NonNull;
20-
import androidx.annotation.Nullable;
2120
import java.util.Objects;
2221

2322
/**
24-
* A {@code AggregateQuerySnapshot} contains results of a {@link AggregateQuery}.
23+
* The results of executing an {@link AggregateQuery}.
2524
*
2625
* <p><b>Subclassing Note</b>: Cloud Firestore classes are not meant to be subclassed except for use
2726
* in test mocks. Subclassing is not supported in production code and new SDK releases may break
2827
* code that does so.
2928
*/
30-
class AggregateQuerySnapshot {
29+
public class AggregateQuerySnapshot {
3130

3231
private final long count;
3332
private final AggregateQuery query;
@@ -38,29 +37,46 @@ class AggregateQuerySnapshot {
3837
this.count = count;
3938
}
4039

41-
/** @return The original {@link AggregateQuery} this snapshot is a result of. */
40+
/** Returns the query that was executed to produce this result. */
4241
@NonNull
4342
public AggregateQuery getQuery() {
4443
return query;
4544
}
4645

47-
/**
48-
* @return The result of a document count aggregation. Returns null if no count aggregation is
49-
* available in the result.
50-
*/
51-
@Nullable
52-
public Long getCount() {
46+
/** Returns the number of documents in the result set of the underlying query. */
47+
public long getCount() {
5348
return count;
5449
}
5550

51+
/**
52+
* Compares this object with the given object for equality.
53+
*
54+
* <p>This object is considered "equal" to the other object if and only if all of the following
55+
* conditions are satisfied:
56+
*
57+
* <ol>
58+
* <li>{@code object} is a non-null instance of {@link AggregateQuerySnapshot}.
59+
* <li>The {@link AggregateQuery} of {@code object} compares equal to that of this object.
60+
* <li>{@code object} has the same results as this object.
61+
* </ol>
62+
*
63+
* @param object The object to compare to this object for equality.
64+
* @return {@code true} if this object is "equal" to the given object, as defined above, or {@code
65+
* false} otherwise.
66+
*/
5667
@Override
57-
public boolean equals(Object o) {
58-
if (this == o) return true;
59-
if (!(o instanceof AggregateQuerySnapshot)) return false;
60-
AggregateQuerySnapshot snapshot = (AggregateQuerySnapshot) o;
61-
return count == snapshot.count && query.equals(snapshot.query);
68+
public boolean equals(Object object) {
69+
if (this == object) return true;
70+
if (!(object instanceof AggregateQuerySnapshot)) return false;
71+
AggregateQuerySnapshot other = (AggregateQuerySnapshot) object;
72+
return count == other.count && query.equals(other.query);
6273
}
6374

75+
/**
76+
* Calculates and returns the hash code for this object.
77+
*
78+
* @return the hash code for this object.
79+
*/
6480
@Override
6581
public int hashCode() {
6682
return Objects.hash(count, query);

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@
1414

1515
package com.google.firebase.firestore;
1616

17-
/** Configures the behavior of {@link AggregateQuery#get}. */
18-
enum AggregateSource {
17+
/**
18+
* The sources from which an {@link AggregateQuery} can retrieve its results.
19+
*
20+
* @see AggregateQuery#get
21+
*/
22+
public enum AggregateSource {
1923
/**
20-
* Reach to the Firestore backend and surface the result verbatim, that is no local documents or
21-
* mutations in the SDK cache will be included in the surfaced result.
24+
* Perform the aggregation on the server and download the result.
2225
*
23-
* <p>Requires client to be online.
26+
* <p>The result received from the server is presented, unaltered, without considering any local
27+
* state. That is, documents in the local cache are not taken into consideration, neither are
28+
* local modifications not yet synchronized with the server. Previously-downloaded results, if
29+
* any, are not used: every request using this source necessarily involves a round trip to the
30+
* server.
31+
*
32+
* <p>The {@link AggregateQuery} will fail if the server cannot be reached, such as if the client
33+
* is offline.
2434
*/
25-
SERVER_DIRECT,
35+
SERVER,
2636
}

0 commit comments

Comments
 (0)