diff --git a/google-cloud-firestore/clirr-ignored-differences.xml b/google-cloud-firestore/clirr-ignored-differences.xml
index 53b346d09..0efc11414 100644
--- a/google-cloud-firestore/clirr-ignored-differences.xml
+++ b/google-cloud-firestore/clirr-ignored-differences.xml
@@ -142,4 +142,19 @@
*
+
+
+ 7012
+ com/google/cloud/firestore/spi/v1/FirestoreRpc
+ com.google.api.gax.rpc.UnaryCallable partitionQueryPagedCallable()
+
+
+ 7006
+ com/google/cloud/firestore/Firestore
+ com.google.cloud.firestore.Query collectionGroup(java.lang.String)
+ com.google.cloud.firestore.CollectionGroup
+
+
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java
new file mode 100644
index 000000000..c2677f087
--- /dev/null
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020 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.gax.rpc.ApiException;
+import com.google.api.gax.rpc.ApiExceptions;
+import com.google.api.gax.rpc.ApiStreamObserver;
+import com.google.cloud.firestore.v1.FirestoreClient;
+import com.google.firestore.v1.Cursor;
+import com.google.firestore.v1.PartitionQueryRequest;
+import javax.annotation.Nullable;
+
+/**
+ * A Collection Group query matches all documents that are contained in a collection or
+ * subcollection with a specific collection ID.
+ */
+public class CollectionGroup extends Query {
+ CollectionGroup(FirestoreRpcContext> rpcContext, String collectionId) {
+ super(
+ rpcContext,
+ QueryOptions.builder()
+ .setParentPath(rpcContext.getResourcePath())
+ .setCollectionId(collectionId)
+ .setAllDescendants(true)
+ .build());
+ }
+
+ /**
+ * Partitions a query by returning partition cursors that can be used to run the query in
+ * parallel. The returned partition cursors are split points that can be used as starting/end
+ * points for the query results.
+ *
+ * @param desiredPartitionCount The desired maximum number of partition points. The number must be
+ * strictly positive. The actual number of partitions returned may be fewer.
+ * @param observer a stream observer that receives the result of the Partition request.
+ */
+ public void getPartitions(
+ long desiredPartitionCount, ApiStreamObserver observer) {
+ // Partition queries require explicit ordering by __name__.
+ Query queryWithDefaultOrder = orderBy(FieldPath.DOCUMENT_ID);
+
+ PartitionQueryRequest.Builder request = PartitionQueryRequest.newBuilder();
+ request.setStructuredQuery(queryWithDefaultOrder.buildQuery());
+ request.setParent(options.getParentPath().toString());
+
+ // Since we are always returning an extra partition (with en empty endBefore cursor), we
+ // reduce the desired partition count by one.
+ request.setPartitionCount(desiredPartitionCount - 1);
+
+ final FirestoreClient.PartitionQueryPagedResponse response;
+ try {
+ response =
+ ApiExceptions.callAndTranslateApiException(
+ rpcContext.sendRequest(
+ request.build(), rpcContext.getClient().partitionQueryPagedCallable()));
+ } catch (ApiException exception) {
+ throw FirestoreException.apiException(exception);
+ }
+
+ @Nullable Object[] lastCursor = null;
+ for (Cursor cursor : response.iterateAll()) {
+ Object[] decodedCursorValue = new Object[cursor.getValuesCount()];
+ for (int i = 0; i < cursor.getValuesCount(); ++i) {
+ decodedCursorValue[i] = UserDataConverter.decodeValue(rpcContext, cursor.getValues(i));
+ }
+ observer.onNext(new QueryPartition(queryWithDefaultOrder, lastCursor, decodedCursorValue));
+ lastCursor = decodedCursorValue;
+ }
+ observer.onNext(new QueryPartition(queryWithDefaultOrder, lastCursor, null));
+ observer.onCompleted();
+ }
+}
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentSnapshot.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentSnapshot.java
index 48008d4c5..6b24ae958 100644
--- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentSnapshot.java
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentSnapshot.java
@@ -23,11 +23,9 @@
import com.google.firestore.v1.Document;
import com.google.firestore.v1.Value;
import com.google.firestore.v1.Write;
-import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
@@ -115,48 +113,6 @@ static DocumentSnapshot fromMissing(
return new DocumentSnapshot(rpcContext, documentReference, null, readTime, null, null);
}
- private Object decodeValue(Value v) {
- Value.ValueTypeCase typeCase = v.getValueTypeCase();
- switch (typeCase) {
- case NULL_VALUE:
- return null;
- case BOOLEAN_VALUE:
- return v.getBooleanValue();
- case INTEGER_VALUE:
- return v.getIntegerValue();
- case DOUBLE_VALUE:
- return v.getDoubleValue();
- case TIMESTAMP_VALUE:
- return Timestamp.fromProto(v.getTimestampValue());
- case STRING_VALUE:
- return v.getStringValue();
- case BYTES_VALUE:
- return Blob.fromByteString(v.getBytesValue());
- case REFERENCE_VALUE:
- String pathName = v.getReferenceValue();
- return new DocumentReference(rpcContext, ResourcePath.create(pathName));
- case GEO_POINT_VALUE:
- return new GeoPoint(
- v.getGeoPointValue().getLatitude(), v.getGeoPointValue().getLongitude());
- case ARRAY_VALUE:
- List