diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateDemo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateDemo.java index b3f4f4f62..92de6afe4 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateDemo.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateDemo.java @@ -17,31 +17,204 @@ package com.google.cloud.firestore; import static com.google.cloud.firestore.AggregateField.count; +import static com.google.cloud.firestore.AggregateField.last; +import static com.google.cloud.firestore.AggregateField.max; +import static com.google.cloud.firestore.AggregateField.min; +import static com.google.cloud.firestore.FieldPath.documentId; +import java.util.HashMap; +import java.util.List; import java.util.Objects; public class AggregateDemo { - public static void Demo1_CountOfDocumentsInACollection(Firestore db) throws Exception { + public static void Demo1A_CountOfDocumentsInACollection(Firestore db) throws Exception { Query query = db.collection("games").document("halo").collection("players"); AggregateSnapshot snapshot = query.aggregate(count()).get().get(); assertEqual(snapshot.get(count()), 5_000_000); } - public static void Demo2_LimitNumberOfDocumentsScannedWithLimit(Firestore db) throws Exception { + public static void Demo1B_LimitNumberOfDocumentsScannedWithLimit(Firestore db) throws Exception { // Limit the work / documents scanned by restricting underlying query. Query query = db.collection("games").document("halo").collection("players").limit(1000); AggregateSnapshot snapshot = query.aggregate(count()).get().get(); assertEqual(snapshot.get(count()), 1000); } - public static void Demo3_LimitNumberOfDocumentsScannedWithUpTo(Firestore db) throws Exception { + public static void Demo1C_LimitNumberOfDocumentsScannedWithUpTo(Firestore db) throws Exception { // Limit the work / documents scanned by specifying upTo on the aggregation. Query query = db.collection("games").document("halo").collection("players"); AggregateSnapshot snapshot = query.aggregate(count().upTo(1000)).get().get(); assertEqual(snapshot.get(count()), 1000); } + public static void Demo2_GroupBySupport(Firestore db) throws Exception { + Query query = db.collectionGroup("players").whereEqualTo("state", "active"); + GroupBySnapshot snapshot = query.groupBy("game").aggregate(count()).get().get(); + assertEqual(snapshot.size(), 3); + List groups = snapshot.getGroups(); + assertEqual(groups.get(0).getString("game"), "cyber_punk"); + assertEqual(groups.get(0).get(count()), 5); + assertEqual(groups.get(1).getString("game"), "halo"); + assertEqual(groups.get(1).get(count()), 55); + assertEqual(groups.get(2).getString("game"), "mine_craft"); + assertEqual(groups.get(2).get(count()), 5_000_000); + } + + public static void Demo3_FieldRenamingAliasing() { + // Aliasing / renaming of aggregations is not exposed from the API surface. + // I've requested that the proto allow non-aggregate fields to also be + // aliased so that the implementation of the Firestore clients can rename + // both aggregate and non-aggregate fields to guarantee that there is NEVER + // a conflict. + } + + public static void Demo4_LimitTheNumberOfDocumentsScanned(Firestore db) throws Exception { + // Limit the work / documents scanned by restricting underlying query. + Query query = db.collection("games").document("halo").collection("players").limit(1000); + AggregateSnapshot snapshot = query.aggregate(count()).get().get(); + assertEqual(snapshot.get(count()), 1000); + } + + public static void Demo5_LimitAggregationBuckets(Firestore db) throws Exception { + Query query = db.collectionGroup("players"); + GroupBySnapshot snapshot = + query.groupBy("game").groupLimit(1).groupOffset(1).aggregate(count()).get().get(); + assertEqual(snapshot.size(), 1); + GroupSnapshot aggregateSnapshot = snapshot.getGroups().get(0); + assertEqual(aggregateSnapshot.getString("game"), "halo"); + assertEqual(aggregateSnapshot.get(count()), 55); + } + + public static void Demo6_LimitWorkPerAggregationBucket(Firestore db) throws Exception { + Query query = db.collection("games").document("halo").collection("players"); + GroupBySnapshot snapshot = query.groupBy("game").aggregate(count().upTo(50)).get().get(); + assertEqual(snapshot.size(), 3); + List groups = snapshot.getGroups(); + assertEqual(groups.get(0).getString("game"), "cyber_punk"); + assertEqual(groups.get(0).get(count()), 5); + assertEqual(groups.get(1).getString("game"), "halo"); + assertEqual(groups.get(1).get(count()), 50); // count is capped at 50 + assertEqual(groups.get(2).getString("game"), "mine_craft"); + assertEqual(groups.get(2).get(count()), 50); // count is capped at 50 + } + + public static void Demo7_OffsetOnNonGroupByQuery() { + // The API does not provide a way to specify an offset for a non-group-by query. + } + + public static void Demo8_PaginationOverAggregationBuckets(Firestore db) throws Exception { + Query query = db.collectionGroup("players").whereEqualTo("state", "active"); + // .orderBy("game") is implied by the group by + GroupBySnapshot snapshot = + query.groupBy("game").startAfterGroup("cyber_punk").aggregate(count()).get().get(); + assertEqual(snapshot.size(), 2); + List groups = snapshot.getGroups(); + assertEqual(groups.get(0).getString("game"), "halo"); + assertEqual(groups.get(0).get(count()), 50); // count is capped at 50 + assertEqual(groups.get(1).getString("game"), "mine_craft"); + assertEqual(groups.get(1).get(count()), 50); // count is capped at 50 + } + + public static void Demo9_ResumeTokens(Firestore db) throws Exception { + Query baseQuery = db.collectionGroup("players").limit(1000).orderBy(documentId()); + long playerCount = 0; + + Query query = baseQuery; + while (true) { + AggregateSnapshot snapshot = query.aggregate(count(), last(documentId())).get().get(); + Long count = snapshot.get(count()); + if (count == null) { + throw new NullPointerException("this should never happen"); + } + + playerCount += count; + if (count < 1000) { + break; + } + + // NOTE: If count==0 then snapshot.getString(last(documentId())) returns null. + String lastDocumentId = snapshot.getString(last(documentId())); + query = baseQuery.startAfter(lastDocumentId); + } + + System.out.println("There are " + playerCount + " players"); + } + + public static void Demo9B_ResumeTokensWithGroupBy(Firestore db) throws Exception { + Query baseQuery = db.collectionGroup("players").limit(1000).orderBy(documentId()); + HashMap countByCountry = new HashMap<>(); + + Query query = baseQuery; + while (true) { + GroupBySnapshot snapshot = + query.groupBy("country").aggregate(count(), last(documentId())).get().get(); + long curTotalCount = 0; + String lastDocumentId = null; + + for (GroupSnapshot group : snapshot.getGroups()) { + String country = group.getString("country"); + Long count = group.get(count()); + if (country == null || count == null) { + throw new NullPointerException("this should never happen"); + } + + if (countByCountry.containsKey(country)) { + countByCountry.put(country, countByCountry.get(country) + count); + } else { + countByCountry.put(country, count); + } + + curTotalCount += count; + + // NOTE: last(documentId()) will be exactly the same for all groups; it just gets repeated + // in each group. + lastDocumentId = group.getString(last(documentId())); + if (lastDocumentId == null) { + if (curTotalCount > 0) { + throw new AssertionError( + "lastDocumentId should only be null if no documents were scanned"); + } + } + } + + if (curTotalCount < 1000) { + break; + } + + query = baseQuery.startAfter(lastDocumentId); + } + + for (String country : countByCountry.keySet()) { + System.out.println(country + " has " + countByCountry.get(country) + " players"); + } + } + + public static void Demo10_Max(Firestore db) throws Exception { + Query query = db.collectionGroup("matches").whereEqualTo("game", "halo").orderBy("user"); + GroupBySnapshot snapshot = query.groupBy("user").aggregate(max("timestamp")).get().get(); + assertEqual(snapshot.size(), 2); + List groups = snapshot.getGroups(); + assertEqual(groups.get(0).getString("user"), "alice"); + assertEqual(groups.get(0).getString(max("timestamp")), "2022-01-06"); + assertEqual(groups.get(1).getString("user"), "bob"); + assertEqual(groups.get(1).getString(max("timestamp")), "2021-12-24"); + } + + public static void Demo11_MultipleAggregations(Firestore db) throws Exception { + Query query = db.collectionGroup("matches").whereEqualTo("game", "halo").orderBy("user"); + GroupBySnapshot snapshot = + query.groupBy("user").aggregate(min("score"), max("score")).get().get(); + assertEqual(snapshot.size(), 2); + List groups = snapshot.getGroups(); + assertEqual(groups.get(0).getString("user"), "alice"); + assertEqual(groups.get(0).getLong(min("score")), 0); + assertEqual(groups.get(0).getLong(max("score")), 500); + assertEqual(groups.get(1).getString("user"), "bob"); + assertEqual(groups.get(1).getLong(min("score")), 50); + assertEqual(groups.get(1).getLong(max("score")), 250); + } + private static void assertEqual(Long num1, Long num2) { if (!Objects.equals(num1, num2)) { throw new AssertionError("num1!=num2"); @@ -51,4 +224,16 @@ private static void assertEqual(Long num1, Long num2) { private static void assertEqual(Long num1, int num2) { assertEqual(num1, Long.valueOf(num2)); } + + private static void assertEqual(Integer num1, Integer num2) { + if (!Objects.equals(num1, num2)) { + throw new AssertionError("num1!=num2"); + } + } + + private static void assertEqual(String num1, String num2) { + if (!Objects.equals(num1, num2)) { + throw new AssertionError("num1!=num2"); + } + } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateField.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateField.java index 97d79aad0..c6f3a63f0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateField.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateField.java @@ -29,6 +29,66 @@ public static CountAggregateField count() { return new CountAggregateField(); } + @Nonnull + public static MinAggregateField min(@Nonnull String field) { + return min(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static MinAggregateField min(@Nonnull FieldPath field) { + return new MinAggregateField(field); + } + + @Nonnull + public static MaxAggregateField max(@Nonnull String field) { + return max(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static MaxAggregateField max(@Nonnull FieldPath field) { + return new MaxAggregateField(field); + } + + @Nonnull + public static AverageAggregateField average(@Nonnull String field) { + return average(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static AverageAggregateField average(@Nonnull FieldPath field) { + return new AverageAggregateField(field); + } + + @Nonnull + public static SumAggregateField sum(@Nonnull String field) { + return sum(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static SumAggregateField sum(@Nonnull FieldPath field) { + return new SumAggregateField(field); + } + + @Nonnull + public static FirstAggregateField first(@Nonnull String field) { + return first(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static FirstAggregateField first(@Nonnull FieldPath field) { + return new FirstAggregateField(field); + } + + @Nonnull + public static LastAggregateField last(@Nonnull String field) { + return last(FieldPath.fromDotSeparatedString(field)); + } + + @Nonnull + public static LastAggregateField last(@Nonnull FieldPath field) { + return new LastAggregateField(field); + } + @Override public abstract boolean equals(Object obj); @@ -78,4 +138,166 @@ public String toString() { } } } + + public static final class MinAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + MinAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MinAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MIN(" + field.toString() + ")"; + } + } + + public static final class MaxAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + MaxAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MaxAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MAX(" + field.toString() + ")"; + } + } + + public static final class AverageAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + AverageAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((AverageAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "AVERAGE(" + field.toString() + ")"; + } + } + + public static final class SumAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + SumAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((SumAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "SUM(" + field.toString() + ")"; + } + } + + public static final class FirstAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + FirstAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((FirstAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "FIRST(" + field.toString() + ")"; + } + } + + public static final class LastAggregateField extends AggregateField { + + @Nonnull private FieldPath field; + + LastAggregateField(@Nonnull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((LastAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "LAST(" + field.toString() + ")"; + } + } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateSnapshot.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateSnapshot.java index 209357eb6..13fc60410 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateSnapshot.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateSnapshot.java @@ -17,6 +17,8 @@ package com.google.cloud.firestore; import com.google.cloud.Timestamp; +import java.util.Date; +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -25,9 +27,53 @@ public interface AggregateSnapshot { @Nonnull Timestamp getReadTime(); + @Nonnull + Map getAggregations(); + + boolean contains(@Nonnull AggregateField field); + + @Nullable + Object get(@Nonnull AggregateField field); + + @Nullable + T get(@Nonnull AggregateField field, @Nonnull Class valueType); + + // Overload get() specifically for COUNT since it has a well-defined type (i.e. long). @Nullable Long get(@Nonnull AggregateField.CountAggregateField field); + // Overload get() specifically for SUM since it has a well-defined type (i.e. double). + @Nullable + Double get(@Nonnull AggregateField.SumAggregateField field); + + // Overload get() specifically for AVERAGE since it has a well-defined type (i.e. double). + @Nullable + Double get(@Nonnull AggregateField.AverageAggregateField field); + + @Nullable + Boolean getBoolean(@Nonnull AggregateField field); + + @Nullable + Double getDouble(@Nonnull AggregateField field); + + @Nullable + String getString(@Nonnull AggregateField field); + + @Nullable + Long getLong(@Nonnull AggregateField field); + + @Nullable + Date getDate(@Nonnull AggregateField field); + + @Nullable + Timestamp getTimestamp(@Nonnull AggregateField field); + + @Nullable + Blob getBlob(@Nonnull AggregateField field); + + @Nullable + GeoPoint getGeoPoint(@Nonnull AggregateField field); + @Override boolean equals(Object obj); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupByQuery.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupByQuery.java new file mode 100644 index 000000000..0aa51e321 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupByQuery.java @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.rpc.ApiStreamObserver; +import com.google.cloud.firestore.Query.Direction; +import java.util.concurrent.Executor; +import javax.annotation.Nonnull; + +public interface GroupByQuery { + + @Nonnull + Query getQuery(); + + @Nonnull + ApiFuture get(); + + void stream(@Nonnull final ApiStreamObserver responseObserver); + + @Nonnull + ListenerRegistration addSnapshotListener(@Nonnull EventListener listener); + + @Nonnull + ListenerRegistration addSnapshotListener( + @Nonnull Executor executor, @Nonnull EventListener listener); + + // Note: Specifying an empty list of aggregates to this method, or not invoking it at all, is + // equivalent to an SQL "DISTINCT" operator. + @Nonnull + GroupByQuery aggregate(@Nonnull AggregateField... fields); + + @Nonnull + GroupByQuery groupLimit(int maxGroups); + + // Question: Do we want to support group-by "limitToLast" queries? In the Query class this is + // implemented entirely client side by issuing the requested query with inverted order-by. We + // would need to verify at runtime that the underlying query has the correct order-by clause and + // possibly invert first/last aggregations to maintain their expected semantics. + @Nonnull + GroupByQuery groupLimitToLast(int maxGroups); + + @Nonnull + GroupByQuery groupOffset(long groupOffset); + + @Nonnull + GroupByQuery startAtGroup(Object... fieldValues); + + @Nonnull + GroupByQuery startAtGroup(@Nonnull GroupSnapshot snapshot); + + @Nonnull + GroupByQuery startAfterGroup(Object... fieldValues); + + @Nonnull + GroupByQuery startAfterGroup(@Nonnull GroupSnapshot snapshot); + + @Nonnull + GroupByQuery endAtGroup(Object... fieldValues); + + @Nonnull + GroupByQuery endAtGroup(@Nonnull GroupSnapshot snapshot); + + @Nonnull + GroupByQuery endBeforeGroup(Object... fieldValues); + + @Nonnull + GroupByQuery endBeforeGroup(@Nonnull GroupSnapshot snapshot); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull String groupByField); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull FieldPath groupByField); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull AggregateField aggregateField); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull String groupByField, @Nonnull Direction direction); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull FieldPath groupByField, @Nonnull Direction direction); + + @Nonnull + GroupByQuery orderByGroup(@Nonnull AggregateField aggregateField, @Nonnull Direction direction); + + @Override + int hashCode(); + + @Override + boolean equals(Object obj); +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupBySnapshot.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupBySnapshot.java new file mode 100644 index 000000000..cffef621b --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupBySnapshot.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore; + +import com.google.cloud.Timestamp; +import java.util.List; +import javax.annotation.Nonnull; + +public interface GroupBySnapshot extends Iterable { + + @Nonnull + GroupByQuery getQuery(); + + @Nonnull + Timestamp getReadTime(); + + @Nonnull + List getGroups(); + + @Nonnull + List getGroupChanges(); + + boolean isEmpty(); + + int size(); + + @Override + boolean equals(Object obj); + + @Override + int hashCode(); +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupChange.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupChange.java new file mode 100644 index 000000000..c9c7db8d6 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupChange.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore; + +import javax.annotation.Nonnull; + +public interface GroupChange { + + enum Type { + ADDED, + MODIFIED, + REMOVED + } + + @Nonnull + Type getType(); + + @Nonnull + GroupSnapshot getGroup(); + + int getOldIndex(); + + int getNewIndex(); + + @Override + boolean equals(Object obj); + + @Override + int hashCode(); +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupSnapshot.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupSnapshot.java new file mode 100644 index 000000000..3389fd1e4 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/GroupSnapshot.java @@ -0,0 +1,93 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore; + +import com.google.cloud.Timestamp; +import java.util.Date; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface GroupSnapshot extends AggregateSnapshot { + + @Nonnull + Map getFields(); + + boolean contains(@Nonnull String field); + + boolean contains(@Nonnull FieldPath field); + + @Nullable + Object get(@Nonnull String field); + + @Nullable + T get(@Nonnull String field, @Nonnull Class valueType); + + @Nullable + Object get(@Nonnull FieldPath field); + + @Nullable + T get(@Nonnull FieldPath field, @Nonnull Class valueType); + + @Nullable + Boolean getBoolean(@Nonnull String field); + + @Nullable + Boolean getBoolean(@Nonnull FieldPath field); + + @Nullable + Double getDouble(@Nonnull String field); + + @Nullable + Double getDouble(@Nonnull FieldPath field); + + @Nullable + String getString(@Nonnull String field); + + @Nullable + String getString(@Nonnull FieldPath field); + + @Nullable + Long getLong(@Nonnull String field); + + @Nullable + Long getLong(@Nonnull FieldPath field); + + @Nullable + Date getDate(@Nonnull String field); + + @Nullable + Date getDate(@Nonnull FieldPath field); + + @Nullable + Timestamp getTimestamp(@Nonnull String field); + + @Nullable + Timestamp getTimestamp(@Nonnull FieldPath field); + + @Nullable + Blob getBlob(@Nonnull String field); + + @Nullable + Blob getBlob(@Nonnull FieldPath field); + + @Nullable + GeoPoint getGeoPoint(@Nonnull String field); + + @Nullable + GeoPoint getGeoPoint(@Nonnull FieldPath field); +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index 14efef2d6..d131d1725 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -1742,7 +1742,8 @@ private boolean isRetryableError(Throwable throwable) { } @Nonnull - public AggregateQuery aggregate(@Nonnull AggregateField field) { + public AggregateQuery aggregate( + @Nonnull AggregateField field, @Nonnull AggregateField... fields) { throw new RuntimeException("not implemented"); } @@ -1754,6 +1755,14 @@ public AggregateQuery count() { return aggregate(AggregateField.count()); } + public GroupByQuery groupBy(@Nonnull String field1, @Nonnull String... fields) { + throw new RuntimeException("not implemented"); + } + + public GroupByQuery groupBy(@Nonnull FieldPath field1, @Nonnull FieldPath... fields) { + throw new RuntimeException("not implemented"); + } + /** * Returns true if this Query is equal to the provided object. *