From 4813432eb73650297d96035de0a786199f2ae6fa Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 4 Oct 2018 15:22:34 -0700 Subject: [PATCH] Add listDocuments() API (#3759) --- .../cloud/firestore/CollectionReference.java | 63 +++++++++++++++++++ .../firestore/spi/v1beta1/FirestoreRpc.java | 6 +- .../spi/v1beta1/GrpcFirestoreRpc.java | 16 +++-- .../cloud/firestore/it/ITSystemTest.java | 33 ++++++++++ 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java index fd41c86851c9..2167026a7504 100644 --- a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java +++ b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java @@ -19,7 +19,14 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.ApiExceptions; +import com.google.cloud.firestore.v1beta1.FirestoreClient.ListDocumentsPagedResponse; import com.google.common.base.Preconditions; +import com.google.firestore.v1beta1.Document; +import com.google.firestore.v1beta1.DocumentMask; +import com.google.firestore.v1beta1.ListDocumentsRequest; +import java.util.Iterator; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -104,6 +111,62 @@ public DocumentReference document(@Nonnull String childPath) { return new DocumentReference(firestore, documentPath); } + /** + * Retrieves the list of documents in this collection. + * + *

The document references returned may include references to "missing documents", i.e. + * document locations that have no document present but which contain subcollections with + * documents. Attempting to read such a document reference (e.g. via `get()` or `onSnapshot()`) + * will return a `DocumentSnapshot` whose `exists()` method returns false. + * + * @return {Promise} @return {Promise} The list of + * documents in this * collection. + */ + @Nonnull + public Iterable listDocuments() { + ListDocumentsRequest.Builder request = ListDocumentsRequest.newBuilder(); + request.setParent(path.getParent().toString()); + request.setCollectionId(this.getId()); + request.setMask(DocumentMask.getDefaultInstance()); + request.setShowMissing(true); + + final ListDocumentsPagedResponse response; + + try { + response = + ApiExceptions.callAndTranslateApiException( + firestore.sendRequest( + request.build(), firestore.getClient().listDocumentsPagedCallable())); + } catch (ApiException exception) { + throw FirestoreException.apiException(exception); + } + + return new Iterable() { + @Override + @Nonnull + public Iterator iterator() { + final Iterator iterator = response.iterateAll().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public DocumentReference next() { + ResourcePath path = ResourcePath.create(iterator.next().getName()); + return document(path.getId()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; + } + }; + } + /** * Adds a new document to this collection with the specified data, assigning it a document ID * automatically. diff --git a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/FirestoreRpc.java b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/FirestoreRpc.java index 3ffb242e1a06..24ba05008fd7 100644 --- a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/FirestoreRpc.java +++ b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/FirestoreRpc.java @@ -21,6 +21,7 @@ import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.ServiceRpc; import com.google.cloud.firestore.v1beta1.FirestoreClient.ListCollectionIdsPagedResponse; +import com.google.cloud.firestore.v1beta1.FirestoreClient.ListDocumentsPagedResponse; import com.google.firestore.v1beta1.BatchGetDocumentsRequest; import com.google.firestore.v1beta1.BatchGetDocumentsResponse; import com.google.firestore.v1beta1.BeginTransactionRequest; @@ -28,13 +29,13 @@ import com.google.firestore.v1beta1.CommitRequest; import com.google.firestore.v1beta1.CommitResponse; import com.google.firestore.v1beta1.ListCollectionIdsRequest; +import com.google.firestore.v1beta1.ListDocumentsRequest; import com.google.firestore.v1beta1.ListenRequest; import com.google.firestore.v1beta1.ListenResponse; import com.google.firestore.v1beta1.RollbackRequest; import com.google.firestore.v1beta1.RunQueryRequest; import com.google.firestore.v1beta1.RunQueryResponse; import com.google.protobuf.Empty; -import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; /** Contains the RPC stubs used by the manual Cloud Firestore client. */ @@ -63,6 +64,9 @@ public interface FirestoreRpc extends AutoCloseable, ServiceRpc { UnaryCallable listCollectionIdsPagedCallable(); + /** Returns a list of documents. */ + UnaryCallable listDocumentsPagedCallable(); + /** Returns a bi-directional watch stream. */ BidiStreamingCallable listenCallable(); } diff --git a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/GrpcFirestoreRpc.java b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/GrpcFirestoreRpc.java index 074105d41274..f9103cb59fab 100644 --- a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/GrpcFirestoreRpc.java +++ b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1beta1/GrpcFirestoreRpc.java @@ -33,10 +33,11 @@ import com.google.cloud.NoCredentials; import com.google.cloud.ServiceOptions; import com.google.cloud.firestore.FirestoreOptions; -import com.google.cloud.firestore.v1beta1.FirestoreSettings; -import com.google.cloud.firestore.v1beta1.stub.FirestoreStubSettings; import com.google.cloud.firestore.v1beta1.FirestoreClient.ListCollectionIdsPagedResponse; +import com.google.cloud.firestore.v1beta1.FirestoreClient.ListDocumentsPagedResponse; +import com.google.cloud.firestore.v1beta1.FirestoreSettings; import com.google.cloud.firestore.v1beta1.stub.FirestoreStub; +import com.google.cloud.firestore.v1beta1.stub.FirestoreStubSettings; import com.google.cloud.firestore.v1beta1.stub.GrpcFirestoreStub; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory; @@ -48,6 +49,7 @@ import com.google.firestore.v1beta1.CommitResponse; import com.google.firestore.v1beta1.DatabaseRootName; import com.google.firestore.v1beta1.ListCollectionIdsRequest; +import com.google.firestore.v1beta1.ListDocumentsRequest; import com.google.firestore.v1beta1.ListenRequest; import com.google.firestore.v1beta1.ListenResponse; import com.google.firestore.v1beta1.RollbackRequest; @@ -59,7 +61,6 @@ import io.grpc.ManagedChannelBuilder; import java.io.IOException; import java.util.Collections; -import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; /** @@ -129,7 +130,8 @@ public Void apply(UnaryCallSettings.Builder builder) { } }; FirestoreStubSettings.Builder firestoreBuilder = - FirestoreStubSettings.newBuilder(clientContext).applyToAllUnaryMethods(retrySettingsSetter); + FirestoreStubSettings.newBuilder(clientContext) + .applyToAllUnaryMethods(retrySettingsSetter); firestoreStub = GrpcFirestoreStub.create(firestoreBuilder.build()); } catch (Exception e) { throw new IOException(e); @@ -187,6 +189,12 @@ public UnaryCallable rollbackCallable() { return firestoreStub.listCollectionIdsPagedCallable(); } + @Override + public UnaryCallable + listDocumentsPagedCallable() { + return firestoreStub.listDocumentsPagedCallable(); + } + @Override public BidiStreamingCallable listenCallable() { return firestoreStub.listenCallable(); diff --git a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java index c072ea50c0e3..d05adfe11f23 100644 --- a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java +++ b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java @@ -577,6 +577,39 @@ public void listCollections() throws Exception { assertEquals(collections.length, count); } + @Test + public void listDocuments() throws Exception { + // We test with 21 documents since 20 documents are by default returned in a single paged + // response. + String[] documents = + new String[] { + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", + "17", "18", "19", "20", "21" + }; + Arrays.sort(documents); // Sort in alphabetical (non-numeric) order. + + WriteBatch batch = firestore.batch(); + for (String document : documents) { + batch.create(randomColl.document(document), SINGLE_FIELD_OBJECT); + } + batch.commit().get(); + + Iterable collectionRefs = randomColl.listDocuments(); + + int count = 0; + for (DocumentReference documentRef : collectionRefs) { + assertEquals(documents[count++], documentRef.getId()); + } + assertEquals(documents.length, count); + } + + @Test + public void listDocumentsListsMissingDocument() throws Exception { + randomColl.document("missing/foo/bar").set(SINGLE_FIELD_MAP).get(); + Iterable collectionRefs = randomColl.listDocuments(); + assertEquals(randomColl.document("missing"), collectionRefs.iterator().next()); + } + @Test public void addAndRemoveFields() throws ExecutionException, InterruptedException { Map expected = new HashMap<>();