firestore: support retrieval of missing documents
DO NOT SUBMIT before the Firestore team approves the surface.
Add two methods:
- CollectionRef.DocumentRefs
- Transaction.DocumentRefs
These return an iterator over all document references in a collection
including those for "missing documents," which have been deleted but
have documents in subcollections.
Fixes #1080.
Change-Id: I804248fcde7d6f8a7401ec80ab471f637f822232
Reviewed-on: https://code-review.googlesource.com/c/32970
Reviewed-by: Jean de Klerk <deklerk@google.com>
diff --git a/firestore/collref.go b/firestore/collref.go
index 0ccea3b..c2e2f5c 100644
--- a/firestore/collref.go
+++ b/firestore/collref.go
@@ -96,6 +96,16 @@
return d, wr, nil
}
+// DocumentRefs returns references to all the documents in the collection, including
+// missing documents. A missing document is a document that does not exist but has
+// sub-documents.
+func (c *CollectionRef) DocumentRefs(ctx context.Context) *DocumentRefIterator {
+ if err := checkTransaction(ctx); err != nil {
+ return &DocumentRefIterator{err: err}
+ }
+ return newDocumentRefIterator(ctx, c, nil)
+}
+
const alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var (
diff --git a/firestore/integration_test.go b/firestore/integration_test.go
index 5f418c3a..00432b8 100644
--- a/firestore/integration_test.go
+++ b/firestore/integration_test.go
@@ -1230,6 +1230,38 @@
codeEq(t, "after cancel", codes.Canceled, err)
}
+func TestIntegration_MissingDocs(t *testing.T) {
+ ctx := context.Background()
+ h := testHelper{t}
+ client := integrationClient(t)
+ coll := client.Collection(collectionIDs.New())
+ dr1 := coll.NewDoc()
+ dr2 := coll.NewDoc()
+ dr3 := dr2.Collection("sub").NewDoc()
+ h.mustCreate(dr1, integrationTestMap)
+ defer h.mustDelete(dr1)
+ h.mustCreate(dr3, integrationTestMap)
+ defer h.mustDelete(dr3)
+
+ // dr1 is a document in coll. dr2 was never created, but there are documents in
+ // its sub-collections. It is "missing".
+ // The Collection.DocumentRefs method includes missing document refs.
+ want := []string{dr1.Path, dr2.Path}
+ drs, err := coll.DocumentRefs(ctx).GetAll()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got []string
+ for _, dr := range drs {
+ got = append(got, dr.Path)
+ }
+ sort.Strings(want)
+ sort.Strings(got)
+ if !testutil.Equal(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+}
+
func codeEq(t *testing.T, msg string, code codes.Code, err error) {
if grpc.Code(err) != code {
t.Fatalf("%s:\ngot <%v>\nwant code %s", msg, err, code)
diff --git a/firestore/list_documents.go b/firestore/list_documents.go
new file mode 100644
index 0000000..5ba70fd
--- /dev/null
+++ b/firestore/list_documents.go
@@ -0,0 +1,103 @@
+// Copyright 2018 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 firestore
+
+import (
+ vkit "cloud.google.com/go/firestore/apiv1beta1"
+ "golang.org/x/net/context"
+ "google.golang.org/api/iterator"
+ pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
+)
+
+// DocumentRefIterator is an interator over DocumentRefs.
+type DocumentRefIterator struct {
+ client *Client
+ it *vkit.DocumentIterator
+ pageInfo *iterator.PageInfo
+ nextFunc func() error
+ items []*DocumentRef
+ err error
+}
+
+func newDocumentRefIterator(ctx context.Context, cr *CollectionRef, tid []byte) *DocumentRefIterator {
+ client := cr.c
+ req := &pb.ListDocumentsRequest{
+ Parent: cr.parentPath,
+ CollectionId: cr.ID,
+ ShowMissing: true,
+ Mask: &pb.DocumentMask{}, // empty mask: we want only the ref
+ }
+ if tid != nil {
+ req.ConsistencySelector = &pb.ListDocumentsRequest_Transaction{tid}
+ }
+ it := &DocumentRefIterator{
+ client: client,
+ it: client.c.ListDocuments(withResourceHeader(ctx, client.path()), req),
+ }
+ it.pageInfo, it.nextFunc = iterator.NewPageInfo(
+ it.fetch,
+ func() int { return len(it.items) },
+ func() interface{} { b := it.items; it.items = nil; return b })
+ return it
+}
+
+// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
+func (it *DocumentRefIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
+
+// Next returns the next result. Its second return value is iterator.Done if there
+// are no more results. Once Next returns Done, all subsequent calls will return
+// Done.
+func (it *DocumentRefIterator) Next() (*DocumentRef, error) {
+ if err := it.nextFunc(); err != nil {
+ return nil, err
+ }
+ item := it.items[0]
+ it.items = it.items[1:]
+ return item, nil
+}
+
+func (it *DocumentRefIterator) fetch(pageSize int, pageToken string) (string, error) {
+ if it.err != nil {
+ return "", it.err
+ }
+ return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
+ docProto, err := it.it.Next()
+ if err != nil {
+ return err
+ }
+ docRef, err := pathToDoc(docProto.Name, it.client)
+ if err != nil {
+ return err
+ }
+ it.items = append(it.items, docRef)
+ return nil
+ })
+}
+
+// GetAll returns all the DocumentRefs remaining from the iterator.
+func (it *DocumentRefIterator) GetAll() ([]*DocumentRef, error) {
+ var drs []*DocumentRef
+ for {
+ dr, err := it.Next()
+ if err == iterator.Done {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ drs = append(drs, dr)
+ }
+ return drs, nil
+}
diff --git a/firestore/transaction.go b/firestore/transaction.go
index efaf106..e273799 100644
--- a/firestore/transaction.go
+++ b/firestore/transaction.go
@@ -235,6 +235,17 @@
}
}
+// DocumentRefs returns references to all the documents in the collection, including
+// missing documents. A missing document is a document that does not exist but has
+// sub-documents.
+func (t *Transaction) DocumentRefs(cr *CollectionRef) *DocumentRefIterator {
+ if len(t.writes) > 0 {
+ t.readAfterWrite = true
+ return &DocumentRefIterator{err: errReadAfterWrite}
+ }
+ return newDocumentRefIterator(t.ctx, cr, t.id)
+}
+
// Create adds a Create operation to the Transaction.
// See DocumentRef.Create for details.
func (t *Transaction) Create(dr *DocumentRef, data interface{}) error {