1// Copyright 2017 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package firestore
16
17import (
18	"context"
19	"crypto/rand"
20	"fmt"
21)
22
23// A CollectionRef is a reference to Firestore collection.
24type CollectionRef struct {
25	c *Client
26
27	// The full resource path of the collection's parent. Typically Parent.Path,
28	// or c.path if Parent is nil. May be different if this CollectionRef was
29	// created from a stored reference to a different project/DB. Always
30	// includes /documents - that is, the parent is minimally considered to be
31	// "<db>/documents".
32	//
33	// For example, "projects/P/databases/D/documents/coll-1/doc-1".
34	parentPath string
35
36	// The shorter resource path of the collection. A collection "coll-2" in
37	// document "doc-1" in collection "coll-1" would be: "coll-1/doc-1/coll-2".
38	selfPath string
39
40	// Parent is the document of which this collection is a part. It is
41	// nil for top-level collections.
42	Parent *DocumentRef
43
44	// The full resource path of the collection: "projects/P/databases/D/documents..."
45	Path string
46
47	// ID is the collection identifier.
48	ID string
49
50	// Use the methods of Query on a CollectionRef to create and run queries.
51	Query
52}
53
54func newTopLevelCollRef(c *Client, dbPath, id string) *CollectionRef {
55	return &CollectionRef{
56		c:          c,
57		ID:         id,
58		parentPath: dbPath + "/documents",
59		selfPath:   id,
60		Path:       dbPath + "/documents/" + id,
61		Query: Query{
62			c:            c,
63			collectionID: id,
64			path:         dbPath + "/documents/" + id,
65			parentPath:   dbPath + "/documents",
66		},
67	}
68}
69
70func newCollRefWithParent(c *Client, parent *DocumentRef, id string) *CollectionRef {
71	selfPath := parent.shortPath + "/" + id
72	return &CollectionRef{
73		c:          c,
74		Parent:     parent,
75		ID:         id,
76		parentPath: parent.Path,
77		selfPath:   selfPath,
78		Path:       parent.Path + "/" + id,
79		Query: Query{
80			c:            c,
81			collectionID: id,
82			path:         parent.Path + "/" + id,
83			parentPath:   parent.Path,
84		},
85	}
86}
87
88// Doc returns a DocumentRef that refers to the document in the collection with the
89// given identifier.
90func (c *CollectionRef) Doc(id string) *DocumentRef {
91	if c == nil {
92		return nil
93	}
94	return newDocRef(c, id)
95}
96
97// NewDoc returns a DocumentRef with a uniquely generated ID.
98//
99// NewDoc will panic if crypto/rand cannot generate enough bytes to make a new
100// doc ID.
101func (c *CollectionRef) NewDoc() *DocumentRef {
102	return c.Doc(uniqueID())
103}
104
105// Add generates a DocumentRef with a unique ID. It then creates the document
106// with the given data, which can be a map[string]interface{}, a struct or a
107// pointer to a struct.
108//
109// Add returns an error in the unlikely event that a document with the same ID
110// already exists.
111func (c *CollectionRef) Add(ctx context.Context, data interface{}) (*DocumentRef, *WriteResult, error) {
112	d := c.NewDoc()
113	wr, err := d.Create(ctx, data)
114	if err != nil {
115		return nil, nil, err
116	}
117	return d, wr, nil
118}
119
120// DocumentRefs returns references to all the documents in the collection, including
121// missing documents. A missing document is a document that does not exist but has
122// sub-documents.
123func (c *CollectionRef) DocumentRefs(ctx context.Context) *DocumentRefIterator {
124	return newDocumentRefIterator(ctx, c, nil)
125}
126
127const alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
128
129func uniqueID() string {
130	b := make([]byte, 20)
131	if _, err := rand.Read(b); err != nil {
132		panic(fmt.Sprintf("firestore: crypto/rand.Read error: %v", err))
133	}
134	for i, byt := range b {
135		b[i] = alphanum[int(byt)%len(alphanum)]
136	}
137	return string(b)
138}
139