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 "errors" 20 "fmt" 21 "io" 22 "reflect" 23 "sort" 24 25 vkit "cloud.google.com/go/firestore/apiv1" 26 "cloud.google.com/go/internal/trace" 27 "google.golang.org/api/iterator" 28 pb "google.golang.org/genproto/googleapis/firestore/v1" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/status" 31) 32 33var errNilDocRef = errors.New("firestore: nil DocumentRef") 34 35// A DocumentRef is a reference to a Firestore document. 36type DocumentRef struct { 37 // The CollectionRef that this document is a part of. Never nil. 38 Parent *CollectionRef 39 40 // The full resource path of the document. A document "doc-1" in collection 41 // "coll-1" would be: "projects/P/databases/D/documents/coll-1/doc-1". 42 Path string 43 44 // The shorter resource path of the document. A document "doc-1" in 45 // collection "coll-1" would be: "coll-1/doc-1". 46 shortPath string 47 48 // The ID of the document: the last component of the resource path. 49 ID string 50} 51 52func newDocRef(parent *CollectionRef, id string) *DocumentRef { 53 return &DocumentRef{ 54 Parent: parent, 55 ID: id, 56 Path: parent.Path + "/" + id, 57 shortPath: parent.selfPath + "/" + id, 58 } 59} 60 61// Collection returns a reference to sub-collection of this document. 62func (d *DocumentRef) Collection(id string) *CollectionRef { 63 return newCollRefWithParent(d.Parent.c, d, id) 64} 65 66// Get retrieves the document. If the document does not exist, Get return a NotFound error, which 67// can be checked with 68// grpc.Code(err) == codes.NotFound 69// In that case, Get returns a non-nil DocumentSnapshot whose Exists method return false and whose 70// ReadTime is the time of the failed read operation. 71func (d *DocumentRef) Get(ctx context.Context) (_ *DocumentSnapshot, err error) { 72 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Get") 73 defer func() { trace.EndSpan(ctx, err) }() 74 75 if d == nil { 76 return nil, errNilDocRef 77 } 78 docsnaps, err := d.Parent.c.getAll(ctx, []*DocumentRef{d}, nil) 79 if err != nil { 80 return nil, err 81 } 82 ds := docsnaps[0] 83 if !ds.Exists() { 84 return ds, status.Errorf(codes.NotFound, "%q not found", d.Path) 85 } 86 return ds, nil 87} 88 89// Create creates the document with the given data. 90// It returns an error if a document with the same ID already exists. 91// 92// The data argument can be a map with string keys, a struct, or a pointer to a 93// struct. The map keys or exported struct fields become the fields of the firestore 94// document. 95// The values of data are converted to Firestore values as follows: 96// 97// - bool converts to Bool. 98// - string converts to String. 99// - int, int8, int16, int32 and int64 convert to Integer. 100// - uint8, uint16 and uint32 convert to Integer. uint, uint64 and uintptr are disallowed, 101// because they may be able to represent values that cannot be represented in an int64, 102// which is the underlying type of a Integer. 103// - float32 and float64 convert to Double. 104// - []byte converts to Bytes. 105// - time.Time and *ts.Timestamp convert to Timestamp. ts is the package 106// "github.com/golang/protobuf/ptypes/timestamp". 107// - *latlng.LatLng converts to GeoPoint. latlng is the package 108// "google.golang.org/genproto/googleapis/type/latlng". You should always use 109// a pointer to a LatLng. 110// - Slices convert to Array. 111// - *firestore.DocumentRef converts to Reference. 112// - Maps and structs convert to Map. 113// - nils of any type convert to Null. 114// 115// Pointers and interface{} are also permitted, and their elements processed 116// recursively. 117// 118// Struct fields can have tags like those used by the encoding/json package. Tags 119// begin with "firestore:" and are followed by "-", meaning "ignore this field," or 120// an alternative name for the field. Following the name, these comma-separated 121// options may be provided: 122// 123// - omitempty: Do not encode this field if it is empty. A value is empty 124// if it is a zero value, or an array, slice or map of length zero. 125// - serverTimestamp: The field must be of type time.Time. When writing, if 126// the field has the zero value, the server will populate the stored document with 127// the time that the request is processed. 128func (d *DocumentRef) Create(ctx context.Context, data interface{}) (_ *WriteResult, err error) { 129 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Create") 130 defer func() { trace.EndSpan(ctx, err) }() 131 132 ws, err := d.newCreateWrites(data) 133 if err != nil { 134 return nil, err 135 } 136 return d.Parent.c.commit1(ctx, ws) 137} 138 139func (d *DocumentRef) newCreateWrites(data interface{}) ([]*pb.Write, error) { 140 if d == nil { 141 return nil, errNilDocRef 142 } 143 doc, transforms, err := toProtoDocument(data) 144 if err != nil { 145 return nil, err 146 } 147 doc.Name = d.Path 148 pc, err := exists(false).preconditionProto() 149 if err != nil { 150 return nil, err 151 } 152 return d.newUpdateWithTransform(doc, nil, pc, transforms, false), nil 153} 154 155// Set creates or overwrites the document with the given data. See DocumentRef.Create 156// for the acceptable values of data. Without options, Set overwrites the document 157// completely. Specify one of the Merge options to preserve an existing document's 158// fields. To delete some fields, use a Merge option with firestore.Delete as the 159// field value. 160func (d *DocumentRef) Set(ctx context.Context, data interface{}, opts ...SetOption) (_ *WriteResult, err error) { 161 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Set") 162 defer func() { trace.EndSpan(ctx, err) }() 163 164 ws, err := d.newSetWrites(data, opts) 165 if err != nil { 166 return nil, err 167 } 168 return d.Parent.c.commit1(ctx, ws) 169} 170 171func (d *DocumentRef) newSetWrites(data interface{}, opts []SetOption) ([]*pb.Write, error) { 172 if d == nil { 173 return nil, errNilDocRef 174 } 175 if data == nil { 176 return nil, errors.New("firestore: nil document contents") 177 } 178 if len(opts) == 0 { // Set without merge 179 doc, serverTimestampPaths, err := toProtoDocument(data) 180 if err != nil { 181 return nil, err 182 } 183 doc.Name = d.Path 184 return d.newUpdateWithTransform(doc, nil, nil, serverTimestampPaths, true), nil 185 } 186 // Set with merge. 187 // This is just like Update, except for the existence precondition. 188 // So we turn data into a list of (FieldPath, interface{}) pairs (fpv's), as we do 189 // for Update. 190 fieldPaths, allPaths, err := processSetOptions(opts) 191 if err != nil { 192 return nil, err 193 } 194 var fpvs []fpv 195 v := reflect.ValueOf(data) 196 if allPaths { 197 // Set with MergeAll. Collect all the leaves of the map. 198 if v.Kind() != reflect.Map { 199 return nil, errors.New("firestore: MergeAll can only be specified with map data") 200 } 201 if v.Len() == 0 { 202 // Special case: MergeAll with an empty map. 203 return d.newUpdateWithTransform(&pb.Document{Name: d.Path}, []FieldPath{}, nil, nil, true), nil 204 } 205 fpvsFromData(v, nil, &fpvs) 206 } else { 207 // Set with merge paths. Collect only the values at the given paths. 208 for _, fp := range fieldPaths { 209 val, err := getAtPath(v, fp) 210 if err != nil { 211 return nil, err 212 } 213 fpvs = append(fpvs, fpv{fp, val}) 214 } 215 } 216 return d.fpvsToWrites(fpvs, nil) 217} 218 219// fpvsFromData converts v into a list of (FieldPath, value) pairs. 220func fpvsFromData(v reflect.Value, prefix FieldPath, fpvs *[]fpv) { 221 switch v.Kind() { 222 case reflect.Map: 223 for _, k := range v.MapKeys() { 224 fpvsFromData(v.MapIndex(k), prefix.with(k.String()), fpvs) 225 } 226 case reflect.Interface: 227 fpvsFromData(v.Elem(), prefix, fpvs) 228 229 default: 230 var val interface{} 231 if v.IsValid() { 232 val = v.Interface() 233 } 234 *fpvs = append(*fpvs, fpv{prefix, val}) 235 } 236} 237 238// Delete deletes the document. If the document doesn't exist, it does nothing 239// and returns no error. 240func (d *DocumentRef) Delete(ctx context.Context, preconds ...Precondition) (_ *WriteResult, err error) { 241 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Delete") 242 defer func() { trace.EndSpan(ctx, err) }() 243 244 ws, err := d.newDeleteWrites(preconds) 245 if err != nil { 246 return nil, err 247 } 248 return d.Parent.c.commit1(ctx, ws) 249} 250 251func (d *DocumentRef) newDeleteWrites(preconds []Precondition) ([]*pb.Write, error) { 252 if d == nil { 253 return nil, errNilDocRef 254 } 255 pc, err := processPreconditionsForDelete(preconds) 256 if err != nil { 257 return nil, err 258 } 259 return []*pb.Write{{ 260 Operation: &pb.Write_Delete{d.Path}, 261 CurrentDocument: pc, 262 }}, nil 263} 264 265func (d *DocumentRef) newUpdatePathWrites(updates []Update, preconds []Precondition) ([]*pb.Write, error) { 266 if len(updates) == 0 { 267 return nil, errors.New("firestore: no paths to update") 268 } 269 var fpvs []fpv 270 for _, u := range updates { 271 v, err := u.process() 272 if err != nil { 273 return nil, err 274 } 275 fpvs = append(fpvs, v) 276 } 277 pc, err := processPreconditionsForUpdate(preconds) 278 if err != nil { 279 return nil, err 280 } 281 return d.fpvsToWrites(fpvs, pc) 282} 283 284func (d *DocumentRef) fpvsToWrites(fpvs []fpv, pc *pb.Precondition) ([]*pb.Write, error) { 285 // Make sure there are no duplications or prefixes among the field paths. 286 var fps []FieldPath 287 for _, fpv := range fpvs { 288 fps = append(fps, fpv.fieldPath) 289 } 290 if err := checkNoDupOrPrefix(fps); err != nil { 291 return nil, err 292 } 293 294 // Process each fpv. 295 var updatePaths []FieldPath 296 var transforms []*pb.DocumentTransform_FieldTransform 297 doc := &pb.Document{ 298 Name: d.Path, 299 Fields: map[string]*pb.Value{}, 300 } 301 for _, fpv := range fpvs { 302 switch fpv.value.(type) { 303 case arrayUnion: 304 au := fpv.value.(arrayUnion) 305 t, err := arrayUnionTransform(au, fpv.fieldPath) 306 if err != nil { 307 return nil, err 308 } 309 transforms = append(transforms, t) 310 case arrayRemove: 311 ar := fpv.value.(arrayRemove) 312 t, err := arrayRemoveTransform(ar, fpv.fieldPath) 313 if err != nil { 314 return nil, err 315 } 316 transforms = append(transforms, t) 317 default: 318 switch fpv.value { 319 case Delete: 320 // Send the field path without a corresponding value. 321 updatePaths = append(updatePaths, fpv.fieldPath) 322 323 case ServerTimestamp: 324 // Use the path in a transform operation. 325 transforms = append(transforms, serverTimestamp(fpv.fieldPath.toServiceFieldPath())) 326 327 default: 328 updatePaths = append(updatePaths, fpv.fieldPath) 329 // Convert the value to a proto and put it into the document. 330 v := reflect.ValueOf(fpv.value) 331 332 pv, _, err := toProtoValue(v) 333 if err != nil { 334 return nil, err 335 } 336 setAtPath(doc.Fields, fpv.fieldPath, pv) 337 // Also accumulate any transforms within the value. 338 ts, err := extractTransforms(v, fpv.fieldPath) 339 if err != nil { 340 return nil, err 341 } 342 transforms = append(transforms, ts...) 343 } 344 } 345 } 346 return d.newUpdateWithTransform(doc, updatePaths, pc, transforms, false), nil 347} 348 349// newUpdateWithTransform constructs operations for a commit. Most generally, it 350// returns an update operation followed by a transform. 351// 352// If there are no serverTimestampPaths, the transform is omitted. 353// 354// If doc.Fields is empty, there are no updatePaths, and there is no precondition, 355// the update is omitted, unless updateOnEmpty is true. 356func (d *DocumentRef) newUpdateWithTransform(doc *pb.Document, updatePaths []FieldPath, pc *pb.Precondition, transforms []*pb.DocumentTransform_FieldTransform, updateOnEmpty bool) []*pb.Write { 357 var ws []*pb.Write 358 if updateOnEmpty || len(doc.Fields) > 0 || 359 len(updatePaths) > 0 || (pc != nil && len(transforms) == 0) { 360 var mask *pb.DocumentMask 361 if updatePaths != nil { 362 sfps := toServiceFieldPaths(updatePaths) 363 sort.Strings(sfps) // TODO(jba): make tests pass without this 364 mask = &pb.DocumentMask{FieldPaths: sfps} 365 } 366 w := &pb.Write{ 367 Operation: &pb.Write_Update{doc}, 368 UpdateMask: mask, 369 CurrentDocument: pc, 370 } 371 ws = append(ws, w) 372 pc = nil // If the precondition is in the write, we don't need it in the transform. 373 } 374 if len(transforms) > 0 || pc != nil { 375 ws = append(ws, &pb.Write{ 376 Operation: &pb.Write_Transform{ 377 Transform: &pb.DocumentTransform{ 378 Document: d.Path, 379 FieldTransforms: transforms, 380 }, 381 }, 382 CurrentDocument: pc, 383 }) 384 } 385 return ws 386} 387 388// arrayUnion is a special type in firestore. It instructs the server to add its 389// elements to whatever array already exists, or to create an array if no value 390// exists. 391type arrayUnion struct { 392 elems []interface{} 393} 394 395// ArrayUnion specifies elements to be added to whatever array already exists in 396// the server, or to create an array if no value exists. 397// 398// If a value exists and it's an array, values are appended to it. Any duplicate 399// value is ignored. 400// If a value exists and it's not an array, the value is replaced by an array of 401// the values in the ArrayUnion. 402// If a value does not exist, an array of the values in the ArrayUnion is created. 403// 404// ArrayUnion must be the value of a field directly; it cannot appear in 405// array or struct values, or in any value that is itself inside an array or 406// struct. 407func ArrayUnion(elems ...interface{}) arrayUnion { 408 return arrayUnion{elems: elems} 409} 410 411// This helper converts an arrayUnion into a proto object. 412func arrayUnionTransform(au arrayUnion, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) { 413 var elems []*pb.Value 414 for _, v := range au.elems { 415 pv, _, err := toProtoValue(reflect.ValueOf(v)) 416 if err != nil { 417 return nil, err 418 } 419 elems = append(elems, pv) 420 } 421 return &pb.DocumentTransform_FieldTransform{ 422 FieldPath: fp.toServiceFieldPath(), 423 TransformType: &pb.DocumentTransform_FieldTransform_AppendMissingElements{ 424 AppendMissingElements: &pb.ArrayValue{Values: elems}, 425 }, 426 }, nil 427} 428 429// arrayRemove is a special type in firestore. It instructs the server to remove 430// the specified values. 431type arrayRemove struct { 432 elems []interface{} 433} 434 435// ArrayRemove specifies elements to be removed from whatever array already 436// exists in the server. 437// 438// If a value exists and it's an array, values are removed from it. All 439// duplicate values are removed. 440// If a value exists and it's not an array, the value is replaced by an empty 441// array. 442// If a value does not exist, an empty array is created. 443// 444// ArrayRemove must be the value of a field directly; it cannot appear in 445// array or struct values, or in any value that is itself inside an array or 446// struct. 447func ArrayRemove(elems ...interface{}) arrayRemove { 448 return arrayRemove{elems: elems} 449} 450 451// This helper converts an arrayRemove into a proto object. 452func arrayRemoveTransform(ar arrayRemove, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) { 453 var elems []*pb.Value 454 for _, v := range ar.elems { 455 // ServerTimestamp cannot occur in an array, so we ignore transformations here. 456 pv, _, err := toProtoValue(reflect.ValueOf(v)) 457 if err != nil { 458 return nil, err 459 } 460 elems = append(elems, pv) 461 } 462 return &pb.DocumentTransform_FieldTransform{ 463 FieldPath: fp.toServiceFieldPath(), 464 TransformType: &pb.DocumentTransform_FieldTransform_RemoveAllFromArray{ 465 RemoveAllFromArray: &pb.ArrayValue{Values: elems}, 466 }, 467 }, nil 468} 469 470type sentinel int 471 472const ( 473 // Delete is used as a value in a call to Update or Set with merge to indicate 474 // that the corresponding key should be deleted. 475 Delete sentinel = iota 476 477 // ServerTimestamp is used as a value in a call to Update to indicate that the 478 // key's value should be set to the time at which the server processed 479 // the request. 480 // 481 // ServerTimestamp must be the value of a field directly; it cannot appear in 482 // array or struct values, or in any value that is itself inside an array or 483 // struct. 484 ServerTimestamp 485) 486 487func (s sentinel) String() string { 488 switch s { 489 case Delete: 490 return "Delete" 491 case ServerTimestamp: 492 return "ServerTimestamp" 493 default: 494 return "<?sentinel?>" 495 } 496} 497 498// An Update describes an update to a value referred to by a path. 499// An Update should have either a non-empty Path or a non-empty FieldPath, 500// but not both. 501// 502// See DocumentRef.Create for acceptable values. 503// To delete a field, specify firestore.Delete as the value. 504type Update struct { 505 Path string // Will be split on dots, and must not contain any of "˜*/[]". 506 FieldPath FieldPath 507 Value interface{} 508} 509 510// An fpv is a pair of validated FieldPath and value. 511type fpv struct { 512 fieldPath FieldPath 513 value interface{} 514} 515 516func (u *Update) process() (fpv, error) { 517 if (u.Path != "") == (u.FieldPath != nil) { 518 return fpv{}, fmt.Errorf("firestore: update %+v should have exactly one of Path or FieldPath", u) 519 } 520 fp := u.FieldPath 521 var err error 522 if fp == nil { 523 fp, err = parseDotSeparatedString(u.Path) 524 if err != nil { 525 return fpv{}, err 526 } 527 } 528 if err := fp.validate(); err != nil { 529 return fpv{}, err 530 } 531 return fpv{fp, u.Value}, nil 532} 533 534// Update updates the document. The values at the given 535// field paths are replaced, but other fields of the stored document are untouched. 536func (d *DocumentRef) Update(ctx context.Context, updates []Update, preconds ...Precondition) (_ *WriteResult, err error) { 537 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Update") 538 defer func() { trace.EndSpan(ctx, err) }() 539 540 ws, err := d.newUpdatePathWrites(updates, preconds) 541 if err != nil { 542 return nil, err 543 } 544 return d.Parent.c.commit1(ctx, ws) 545} 546 547// Collections returns an iterator over the immediate sub-collections of the document. 548func (d *DocumentRef) Collections(ctx context.Context) *CollectionIterator { 549 client := d.Parent.c 550 it := &CollectionIterator{ 551 client: client, 552 parent: d, 553 it: client.c.ListCollectionIds( 554 withResourceHeader(ctx, client.path()), 555 &pb.ListCollectionIdsRequest{Parent: d.Path}), 556 } 557 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 558 it.fetch, 559 func() int { return len(it.items) }, 560 func() interface{} { b := it.items; it.items = nil; return b }) 561 return it 562} 563 564// CollectionIterator is an iterator over sub-collections of a document. 565type CollectionIterator struct { 566 client *Client 567 parent *DocumentRef 568 it *vkit.StringIterator 569 pageInfo *iterator.PageInfo 570 nextFunc func() error 571 items []*CollectionRef 572 err error 573} 574 575// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 576func (it *CollectionIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 577 578// Next returns the next result. Its second return value is iterator.Done if there 579// are no more results. Once Next returns Done, all subsequent calls will return 580// Done. 581func (it *CollectionIterator) Next() (*CollectionRef, error) { 582 if err := it.nextFunc(); err != nil { 583 return nil, err 584 } 585 item := it.items[0] 586 it.items = it.items[1:] 587 return item, nil 588} 589 590func (it *CollectionIterator) fetch(pageSize int, pageToken string) (string, error) { 591 if it.err != nil { 592 return "", it.err 593 } 594 return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error { 595 id, err := it.it.Next() 596 if err != nil { 597 return err 598 } 599 var cr *CollectionRef 600 if it.parent == nil { 601 cr = newTopLevelCollRef(it.client, it.client.path(), id) 602 } else { 603 cr = newCollRefWithParent(it.client, it.parent, id) 604 } 605 it.items = append(it.items, cr) 606 return nil 607 }) 608} 609 610// GetAll returns all the collections remaining from the iterator. 611func (it *CollectionIterator) GetAll() ([]*CollectionRef, error) { 612 var crs []*CollectionRef 613 for { 614 cr, err := it.Next() 615 if err == iterator.Done { 616 break 617 } 618 if err != nil { 619 return nil, err 620 } 621 crs = append(crs, cr) 622 } 623 return crs, nil 624} 625 626// Common fetch code for iterators that are backed by vkit iterators. 627// TODO(jba): dedup with same function in logging/logadmin. 628func iterFetch(pageSize int, pageToken string, pi *iterator.PageInfo, next func() error) (string, error) { 629 pi.MaxSize = pageSize 630 pi.Token = pageToken 631 // Get one item, which will fill the buffer. 632 if err := next(); err != nil { 633 return "", err 634 } 635 // Collect the rest of the buffer. 636 for pi.Remaining() > 0 { 637 if err := next(); err != nil { 638 return "", err 639 } 640 } 641 return pi.Token, nil 642} 643 644// Snapshots returns an iterator over snapshots of the document. Each time the document 645// changes or is added or deleted, a new snapshot will be generated. 646func (d *DocumentRef) Snapshots(ctx context.Context) *DocumentSnapshotIterator { 647 return &DocumentSnapshotIterator{ 648 docref: d, 649 ws: newWatchStreamForDocument(ctx, d), 650 } 651} 652 653// DocumentSnapshotIterator is an iterator over snapshots of a document. 654// Call Next on the iterator to get a snapshot of the document each time it changes. 655// Call Stop on the iterator when done. 656// 657// For an example, see DocumentRef.Snapshots. 658type DocumentSnapshotIterator struct { 659 docref *DocumentRef 660 ws *watchStream 661} 662 663// Next blocks until the document changes, then returns the DocumentSnapshot for 664// the current state of the document. If the document has been deleted, Next 665// returns a DocumentSnapshot whose Exists method returns false. 666// 667// Next never returns iterator.Done unless it is called after Stop. 668func (it *DocumentSnapshotIterator) Next() (*DocumentSnapshot, error) { 669 btree, _, readTime, err := it.ws.nextSnapshot() 670 if err != nil { 671 if err == io.EOF { 672 err = iterator.Done 673 } 674 // watchStream's error is sticky, so SnapshotIterator does not need to remember it. 675 return nil, err 676 } 677 if btree.Len() == 0 { // document deleted 678 return &DocumentSnapshot{Ref: it.docref, ReadTime: readTime}, nil 679 } 680 snap, _ := btree.At(0) 681 return snap.(*DocumentSnapshot), nil 682} 683 684// Stop stops receiving snapshots. You should always call Stop when you are done with 685// a DocumentSnapshotIterator, to free up resources. It is not safe to call Stop 686// concurrently with Next. 687func (it *DocumentSnapshotIterator) Stop() { 688 it.ws.stop() 689} 690