You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

284 lines
7.7 KiB

  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. package firestore
  15. import (
  16. "errors"
  17. "fmt"
  18. "io"
  19. "strings"
  20. "time"
  21. "google.golang.org/api/iterator"
  22. vkit "cloud.google.com/go/firestore/apiv1beta1"
  23. "cloud.google.com/go/internal/version"
  24. pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
  25. "github.com/golang/protobuf/ptypes"
  26. gax "github.com/googleapis/gax-go"
  27. "golang.org/x/net/context"
  28. "google.golang.org/api/option"
  29. "google.golang.org/grpc/codes"
  30. "google.golang.org/grpc/metadata"
  31. "google.golang.org/grpc/status"
  32. )
  33. // resourcePrefixHeader is the name of the metadata header used to indicate
  34. // the resource being operated on.
  35. const resourcePrefixHeader = "google-cloud-resource-prefix"
  36. // A Client provides access to the Firestore service.
  37. type Client struct {
  38. c *vkit.Client
  39. projectID string
  40. databaseID string // A client is tied to a single database.
  41. }
  42. // NewClient creates a new Firestore client that uses the given project.
  43. func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
  44. vc, err := vkit.NewClient(ctx, opts...)
  45. if err != nil {
  46. return nil, err
  47. }
  48. vc.SetGoogleClientInfo("gccl", version.Repo)
  49. c := &Client{
  50. c: vc,
  51. projectID: projectID,
  52. databaseID: "(default)", // always "(default)", for now
  53. }
  54. return c, nil
  55. }
  56. // Close closes any resources held by the client.
  57. //
  58. // Close need not be called at program exit.
  59. func (c *Client) Close() error {
  60. return c.c.Close()
  61. }
  62. func (c *Client) path() string {
  63. return fmt.Sprintf("projects/%s/databases/%s", c.projectID, c.databaseID)
  64. }
  65. func withResourceHeader(ctx context.Context, resource string) context.Context {
  66. md, _ := metadata.FromOutgoingContext(ctx)
  67. md = md.Copy()
  68. md[resourcePrefixHeader] = []string{resource}
  69. return metadata.NewOutgoingContext(ctx, md)
  70. }
  71. // Collection creates a reference to a collection with the given path.
  72. // A path is a sequence of IDs separated by slashes.
  73. //
  74. // Collection returns nil if path contains an even number of IDs or any ID is empty.
  75. func (c *Client) Collection(path string) *CollectionRef {
  76. coll, _ := c.idsToRef(strings.Split(path, "/"), c.path())
  77. return coll
  78. }
  79. // Doc creates a reference to a document with the given path.
  80. // A path is a sequence of IDs separated by slashes.
  81. //
  82. // Doc returns nil if path contains an odd number of IDs or any ID is empty.
  83. func (c *Client) Doc(path string) *DocumentRef {
  84. _, doc := c.idsToRef(strings.Split(path, "/"), c.path())
  85. return doc
  86. }
  87. func (c *Client) idsToRef(IDs []string, dbPath string) (*CollectionRef, *DocumentRef) {
  88. if len(IDs) == 0 {
  89. return nil, nil
  90. }
  91. for _, id := range IDs {
  92. if id == "" {
  93. return nil, nil
  94. }
  95. }
  96. coll := newTopLevelCollRef(c, dbPath, IDs[0])
  97. i := 1
  98. for i < len(IDs) {
  99. doc := newDocRef(coll, IDs[i])
  100. i++
  101. if i == len(IDs) {
  102. return nil, doc
  103. }
  104. coll = newCollRefWithParent(c, doc, IDs[i])
  105. i++
  106. }
  107. return coll, nil
  108. }
  109. // GetAll retrieves multiple documents with a single call. The DocumentSnapshots are
  110. // returned in the order of the given DocumentRefs.
  111. //
  112. // If a document is not present, the corresponding DocumentSnapshot's Exists method will return false.
  113. func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*DocumentSnapshot, error) {
  114. if err := checkTransaction(ctx); err != nil {
  115. return nil, err
  116. }
  117. return c.getAll(ctx, docRefs, nil)
  118. }
  119. func (c *Client) getAll(ctx context.Context, docRefs []*DocumentRef, tid []byte) ([]*DocumentSnapshot, error) {
  120. var docNames []string
  121. docIndex := map[string]int{} // doc name to position in docRefs
  122. for i, dr := range docRefs {
  123. if dr == nil {
  124. return nil, errNilDocRef
  125. }
  126. docNames = append(docNames, dr.Path)
  127. docIndex[dr.Path] = i
  128. }
  129. req := &pb.BatchGetDocumentsRequest{
  130. Database: c.path(),
  131. Documents: docNames,
  132. }
  133. if tid != nil {
  134. req.ConsistencySelector = &pb.BatchGetDocumentsRequest_Transaction{tid}
  135. }
  136. streamClient, err := c.c.BatchGetDocuments(withResourceHeader(ctx, req.Database), req)
  137. if err != nil {
  138. return nil, err
  139. }
  140. // Read and remember all results from the stream.
  141. var resps []*pb.BatchGetDocumentsResponse
  142. for {
  143. resp, err := streamClient.Recv()
  144. if err == io.EOF {
  145. break
  146. }
  147. if err != nil {
  148. return nil, err
  149. }
  150. resps = append(resps, resp)
  151. }
  152. // Results may arrive out of order. Put each at the right index.
  153. docs := make([]*DocumentSnapshot, len(docNames))
  154. for _, resp := range resps {
  155. var (
  156. i int
  157. doc *pb.Document
  158. err error
  159. )
  160. switch r := resp.Result.(type) {
  161. case *pb.BatchGetDocumentsResponse_Found:
  162. i = docIndex[r.Found.Name]
  163. doc = r.Found
  164. case *pb.BatchGetDocumentsResponse_Missing:
  165. i = docIndex[r.Missing]
  166. doc = nil
  167. default:
  168. return nil, errors.New("firestore: unknown BatchGetDocumentsResponse result type")
  169. }
  170. if docs[i] != nil {
  171. return nil, fmt.Errorf("firestore: %q seen twice", docRefs[i].Path)
  172. }
  173. docs[i], err = newDocumentSnapshot(docRefs[i], doc, c, resp.ReadTime)
  174. if err != nil {
  175. return nil, err
  176. }
  177. }
  178. return docs, nil
  179. }
  180. // Collections returns an interator over the top-level collections.
  181. func (c *Client) Collections(ctx context.Context) *CollectionIterator {
  182. it := &CollectionIterator{
  183. err: checkTransaction(ctx),
  184. client: c,
  185. it: c.c.ListCollectionIds(
  186. withResourceHeader(ctx, c.path()),
  187. &pb.ListCollectionIdsRequest{Parent: c.path()}),
  188. }
  189. it.pageInfo, it.nextFunc = iterator.NewPageInfo(
  190. it.fetch,
  191. func() int { return len(it.items) },
  192. func() interface{} { b := it.items; it.items = nil; return b })
  193. return it
  194. }
  195. // Batch returns a WriteBatch.
  196. func (c *Client) Batch() *WriteBatch {
  197. return &WriteBatch{c: c}
  198. }
  199. // commit calls the Commit RPC outside of a transaction.
  200. func (c *Client) commit(ctx context.Context, ws []*pb.Write) ([]*WriteResult, error) {
  201. if err := checkTransaction(ctx); err != nil {
  202. return nil, err
  203. }
  204. req := &pb.CommitRequest{
  205. Database: c.path(),
  206. Writes: ws,
  207. }
  208. res, err := c.c.Commit(withResourceHeader(ctx, req.Database), req)
  209. if err != nil {
  210. return nil, err
  211. }
  212. if len(res.WriteResults) == 0 {
  213. return nil, errors.New("firestore: missing WriteResult")
  214. }
  215. var wrs []*WriteResult
  216. for _, pwr := range res.WriteResults {
  217. wr, err := writeResultFromProto(pwr)
  218. if err != nil {
  219. return nil, err
  220. }
  221. wrs = append(wrs, wr)
  222. }
  223. return wrs, nil
  224. }
  225. func (c *Client) commit1(ctx context.Context, ws []*pb.Write) (*WriteResult, error) {
  226. wrs, err := c.commit(ctx, ws)
  227. if err != nil {
  228. return nil, err
  229. }
  230. return wrs[0], nil
  231. }
  232. // A WriteResult is returned by methods that write documents.
  233. type WriteResult struct {
  234. // The time at which the document was updated, or created if it did not
  235. // previously exist. Writes that do not actually change the document do
  236. // not change the update time.
  237. UpdateTime time.Time
  238. }
  239. func writeResultFromProto(wr *pb.WriteResult) (*WriteResult, error) {
  240. t, err := ptypes.Timestamp(wr.UpdateTime)
  241. if err != nil {
  242. t = time.Time{}
  243. // TODO(jba): Follow up if Delete is supposed to return a nil timestamp.
  244. }
  245. return &WriteResult{UpdateTime: t}, nil
  246. }
  247. func sleep(ctx context.Context, dur time.Duration) error {
  248. switch err := gax.Sleep(ctx, dur); err {
  249. case context.Canceled:
  250. return status.Error(codes.Canceled, "context canceled")
  251. case context.DeadlineExceeded:
  252. return status.Error(codes.DeadlineExceeded, "context deadline exceeded")
  253. default:
  254. return err
  255. }
  256. }