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	"math/rand"
20	"os"
21	"sync"
22	"time"
23)
24
25// A CollectionRef is a reference to Firestore collection.
26type CollectionRef struct {
27	c *Client
28
29	// The full resource path of the collection's parent. Typically Parent.Path,
30	// or c.path if Parent is nil. May be different if this CollectionRef was
31	// created from a stored reference to a different project/DB. Always
32	// includes /documents - that is, the parent is minimally considered to be
33	// "<db>/documents".
34	//
35	// For example, "projects/P/databases/D/documents/coll-1/doc-1".
36	parentPath string
37
38	// The shorter resource path of the collection. A collection "coll-2" in
39	// document "doc-1" in collection "coll-1" would be: "coll-1/doc-1/coll-2".
40	selfPath string
41
42	// Parent is the document of which this collection is a part. It is
43	// nil for top-level collections.
44	Parent *DocumentRef
45
46	// The full resource path of the collection: "projects/P/databases/D/documents..."
47	Path string
48
49	// ID is the collection identifier.
50	ID string
51
52	// Use the methods of Query on a CollectionRef to create and run queries.
53	Query
54}
55
56func newTopLevelCollRef(c *Client, dbPath, id string) *CollectionRef {
57	return &CollectionRef{
58		c:          c,
59		ID:         id,
60		parentPath: dbPath + "/documents",
61		selfPath:   id,
62		Path:       dbPath + "/documents/" + id,
63		Query: Query{
64			c:            c,
65			collectionID: id,
66			path:         dbPath + "/documents/" + id,
67			parentPath:   dbPath + "/documents",
68		},
69	}
70}
71
72func newCollRefWithParent(c *Client, parent *DocumentRef, id string) *CollectionRef {
73	selfPath := parent.shortPath + "/" + id
74	return &CollectionRef{
75		c:          c,
76		Parent:     parent,
77		ID:         id,
78		parentPath: parent.Path,
79		selfPath:   selfPath,
80		Path:       parent.Path + "/" + id,
81		Query: Query{
82			c:            c,
83			collectionID: id,
84			path:         parent.Path + "/" + id,
85			parentPath:   parent.Path,
86		},
87	}
88}
89
90// Doc returns a DocumentRef that refers to the document in the collection with the
91// given identifier.
92func (c *CollectionRef) Doc(id string) *DocumentRef {
93	if c == nil {
94		return nil
95	}
96	return newDocRef(c, id)
97}
98
99// NewDoc returns a DocumentRef with a uniquely generated ID.
100func (c *CollectionRef) NewDoc() *DocumentRef {
101	return c.Doc(uniqueID())
102}
103
104// Add generates a DocumentRef with a unique ID. It then creates the document
105// with the given data, which can be a map[string]interface{}, a struct or a
106// pointer to a struct.
107//
108// Add returns an error in the unlikely event that a document with the same ID
109// already exists.
110func (c *CollectionRef) Add(ctx context.Context, data interface{}) (*DocumentRef, *WriteResult, error) {
111	d := c.NewDoc()
112	wr, err := d.Create(ctx, data)
113	if err != nil {
114		return nil, nil, err
115	}
116	return d, wr, nil
117}
118
119// DocumentRefs returns references to all the documents in the collection, including
120// missing documents. A missing document is a document that does not exist but has
121// sub-documents.
122func (c *CollectionRef) DocumentRefs(ctx context.Context) *DocumentRefIterator {
123	return newDocumentRefIterator(ctx, c, nil)
124}
125
126const alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
127
128var (
129	rngMu sync.Mutex
130	rng   = rand.New(rand.NewSource(time.Now().UnixNano() ^ int64(os.Getpid())))
131)
132
133func uniqueID() string {
134	var b [20]byte
135	rngMu.Lock()
136	for i := 0; i < len(b); i++ {
137		b[i] = alphanum[rng.Intn(len(alphanum))]
138	}
139	rngMu.Unlock()
140	return string(b[:])
141}
142