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// status.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. serverTimestamp 126// is a sentinel token that tells Firestore to substitute the server time 127// into that field. When writing, if the field has the zero value, the 128// server will populate the stored document with the time that the request 129// is processed. However, if the field value is non-zero it won't be saved. 130func (d *DocumentRef) Create(ctx context.Context, data interface{}) (_ *WriteResult, err error) { 131 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Create") 132 defer func() { trace.EndSpan(ctx, err) }() 133 134 ws, err := d.newCreateWrites(data) 135 if err != nil { 136 return nil, err 137 } 138 return d.Parent.c.commit1(ctx, ws) 139} 140 141func (d *DocumentRef) newCreateWrites(data interface{}) ([]*pb.Write, error) { 142 if d == nil { 143 return nil, errNilDocRef 144 } 145 doc, transforms, err := toProtoDocument(data) 146 if err != nil { 147 return nil, err 148 } 149 doc.Name = d.Path 150 pc, err := exists(false).preconditionProto() 151 if err != nil { 152 return nil, err 153 } 154 return d.newUpdateWithTransform(doc, nil, pc, transforms, false), nil 155} 156 157// Set creates or overwrites the document with the given data. See DocumentRef.Create 158// for the acceptable values of data. Without options, Set overwrites the document 159// completely. Specify one of the Merge options to preserve an existing document's 160// fields. To delete some fields, use a Merge option with firestore.Delete as the 161// field value. 162func (d *DocumentRef) Set(ctx context.Context, data interface{}, opts ...SetOption) (_ *WriteResult, err error) { 163 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Set") 164 defer func() { trace.EndSpan(ctx, err) }() 165 166 ws, err := d.newSetWrites(data, opts) 167 if err != nil { 168 return nil, err 169 } 170 return d.Parent.c.commit1(ctx, ws) 171} 172 173func (d *DocumentRef) newSetWrites(data interface{}, opts []SetOption) ([]*pb.Write, error) { 174 if d == nil { 175 return nil, errNilDocRef 176 } 177 if data == nil { 178 return nil, errors.New("firestore: nil document contents") 179 } 180 if len(opts) == 0 { // Set without merge 181 doc, serverTimestampPaths, err := toProtoDocument(data) 182 if err != nil { 183 return nil, err 184 } 185 doc.Name = d.Path 186 return d.newUpdateWithTransform(doc, nil, nil, serverTimestampPaths, true), nil 187 } 188 // Set with merge. 189 // This is just like Update, except for the existence precondition. 190 // So we turn data into a list of (FieldPath, interface{}) pairs (fpv's), as we do 191 // for Update. 192 fieldPaths, allPaths, err := processSetOptions(opts) 193 if err != nil { 194 return nil, err 195 } 196 var fpvs []fpv 197 v := reflect.ValueOf(data) 198 if allPaths { 199 // Set with MergeAll. Collect all the leaves of the map. 200 if v.Kind() != reflect.Map { 201 return nil, errors.New("firestore: MergeAll can only be specified with map data") 202 } 203 if v.Len() == 0 { 204 // Special case: MergeAll with an empty map. 205 return d.newUpdateWithTransform(&pb.Document{Name: d.Path}, []FieldPath{}, nil, nil, true), nil 206 } 207 fpvsFromData(v, nil, &fpvs) 208 } else { 209 // Set with merge paths. Collect only the values at the given paths. 210 for _, fp := range fieldPaths { 211 val, err := getAtPath(v, fp) 212 if err != nil { 213 return nil, err 214 } 215 fpvs = append(fpvs, fpv{fp, val}) 216 } 217 } 218 return d.fpvsToWrites(fpvs, nil) 219} 220 221// fpvsFromData converts v into a list of (FieldPath, value) pairs. 222func fpvsFromData(v reflect.Value, prefix FieldPath, fpvs *[]fpv) { 223 switch v.Kind() { 224 case reflect.Map: 225 for _, k := range v.MapKeys() { 226 fpvsFromData(v.MapIndex(k), prefix.with(k.String()), fpvs) 227 } 228 case reflect.Interface: 229 fpvsFromData(v.Elem(), prefix, fpvs) 230 231 default: 232 var val interface{} 233 if v.IsValid() { 234 val = v.Interface() 235 } 236 *fpvs = append(*fpvs, fpv{prefix, val}) 237 } 238} 239 240// Delete deletes the document. If the document doesn't exist, it does nothing 241// and returns no error. 242func (d *DocumentRef) Delete(ctx context.Context, preconds ...Precondition) (_ *WriteResult, err error) { 243 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Delete") 244 defer func() { trace.EndSpan(ctx, err) }() 245 246 ws, err := d.newDeleteWrites(preconds) 247 if err != nil { 248 return nil, err 249 } 250 return d.Parent.c.commit1(ctx, ws) 251} 252 253func (d *DocumentRef) newDeleteWrites(preconds []Precondition) ([]*pb.Write, error) { 254 if d == nil { 255 return nil, errNilDocRef 256 } 257 pc, err := processPreconditionsForDelete(preconds) 258 if err != nil { 259 return nil, err 260 } 261 return []*pb.Write{{ 262 Operation: &pb.Write_Delete{d.Path}, 263 CurrentDocument: pc, 264 }}, nil 265} 266 267func (d *DocumentRef) newUpdatePathWrites(updates []Update, preconds []Precondition) ([]*pb.Write, error) { 268 if len(updates) == 0 { 269 return nil, errors.New("firestore: no paths to update") 270 } 271 var fpvs []fpv 272 for _, u := range updates { 273 v, err := u.process() 274 if err != nil { 275 return nil, err 276 } 277 fpvs = append(fpvs, v) 278 } 279 pc, err := processPreconditionsForUpdate(preconds) 280 if err != nil { 281 return nil, err 282 } 283 return d.fpvsToWrites(fpvs, pc) 284} 285 286func (d *DocumentRef) fpvsToWrites(fpvs []fpv, pc *pb.Precondition) ([]*pb.Write, error) { 287 // Make sure there are no duplications or prefixes among the field paths. 288 var fps []FieldPath 289 for _, fpv := range fpvs { 290 fps = append(fps, fpv.fieldPath) 291 } 292 if err := checkNoDupOrPrefix(fps); err != nil { 293 return nil, err 294 } 295 296 // Process each fpv. 297 var updatePaths []FieldPath 298 var transforms []*pb.DocumentTransform_FieldTransform 299 doc := &pb.Document{ 300 Name: d.Path, 301 Fields: map[string]*pb.Value{}, 302 } 303 for _, fpv := range fpvs { 304 switch fpv.value.(type) { 305 case arrayUnion: 306 au := fpv.value.(arrayUnion) 307 t, err := arrayUnionTransform(au, fpv.fieldPath) 308 if err != nil { 309 return nil, err 310 } 311 transforms = append(transforms, t) 312 case arrayRemove: 313 ar := fpv.value.(arrayRemove) 314 t, err := arrayRemoveTransform(ar, fpv.fieldPath) 315 if err != nil { 316 return nil, err 317 } 318 transforms = append(transforms, t) 319 case transform: 320 t, err := fieldTransform(fpv.value.(transform), fpv.fieldPath) 321 if err != nil { 322 return nil, err 323 } 324 transforms = append(transforms, t) 325 326 default: 327 switch fpv.value { 328 case Delete: 329 // Send the field path without a corresponding value. 330 updatePaths = append(updatePaths, fpv.fieldPath) 331 332 case ServerTimestamp: 333 // Use the path in a transform operation. 334 transforms = append(transforms, serverTimestamp(fpv.fieldPath.toServiceFieldPath())) 335 336 default: 337 updatePaths = append(updatePaths, fpv.fieldPath) 338 // Convert the value to a proto and put it into the document. 339 v := reflect.ValueOf(fpv.value) 340 341 pv, _, err := toProtoValue(v) 342 if err != nil { 343 return nil, err 344 } 345 setAtPath(doc.Fields, fpv.fieldPath, pv) 346 // Also accumulate any transforms within the value. 347 ts, err := extractTransforms(v, fpv.fieldPath) 348 if err != nil { 349 return nil, err 350 } 351 transforms = append(transforms, ts...) 352 } 353 } 354 } 355 return d.newUpdateWithTransform(doc, updatePaths, pc, transforms, false), nil 356} 357 358// newUpdateWithTransform constructs operations for a commit. Most generally, it 359// returns an update operation followed by a transform. 360// 361// If there are no serverTimestampPaths, the transform is omitted. 362// 363// If doc.Fields is empty, there are no updatePaths, and there is no precondition, 364// the update is omitted, unless updateOnEmpty is true. 365func (d *DocumentRef) newUpdateWithTransform(doc *pb.Document, updatePaths []FieldPath, pc *pb.Precondition, transforms []*pb.DocumentTransform_FieldTransform, updateOnEmpty bool) []*pb.Write { 366 var ws []*pb.Write 367 if updateOnEmpty || len(doc.Fields) > 0 || 368 len(updatePaths) > 0 || (pc != nil && len(transforms) == 0) { 369 var mask *pb.DocumentMask 370 if updatePaths != nil { 371 sfps := toServiceFieldPaths(updatePaths) 372 sort.Strings(sfps) // TODO(jba): make tests pass without this 373 mask = &pb.DocumentMask{FieldPaths: sfps} 374 } 375 w := &pb.Write{ 376 Operation: &pb.Write_Update{doc}, 377 UpdateMask: mask, 378 CurrentDocument: pc, 379 } 380 ws = append(ws, w) 381 pc = nil // If the precondition is in the write, we don't need it in the transform. 382 } 383 if len(transforms) > 0 || pc != nil { 384 ws = append(ws, &pb.Write{ 385 Operation: &pb.Write_Transform{ 386 Transform: &pb.DocumentTransform{ 387 Document: d.Path, 388 FieldTransforms: transforms, 389 }, 390 }, 391 CurrentDocument: pc, 392 }) 393 } 394 return ws 395} 396 397// arrayUnion is a special type in firestore. It instructs the server to add its 398// elements to whatever array already exists, or to create an array if no value 399// exists. 400type arrayUnion struct { 401 elems []interface{} 402} 403 404// ArrayUnion specifies elements to be added to whatever array already exists in 405// the server, or to create an array if no value exists. 406// 407// If a value exists and it's an array, values are appended to it. Any duplicate 408// value is ignored. 409// If a value exists and it's not an array, the value is replaced by an array of 410// the values in the ArrayUnion. 411// If a value does not exist, an array of the values in the ArrayUnion is created. 412// 413// ArrayUnion must be the value of a field directly; it cannot appear in 414// array or struct values, or in any value that is itself inside an array or 415// struct. 416func ArrayUnion(elems ...interface{}) arrayUnion { 417 return arrayUnion{elems: elems} 418} 419 420// This helper converts an arrayUnion into a proto object. 421func arrayUnionTransform(au arrayUnion, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) { 422 var elems []*pb.Value 423 for _, v := range au.elems { 424 pv, _, err := toProtoValue(reflect.ValueOf(v)) 425 if err != nil { 426 return nil, err 427 } 428 elems = append(elems, pv) 429 } 430 return &pb.DocumentTransform_FieldTransform{ 431 FieldPath: fp.toServiceFieldPath(), 432 TransformType: &pb.DocumentTransform_FieldTransform_AppendMissingElements{ 433 AppendMissingElements: &pb.ArrayValue{Values: elems}, 434 }, 435 }, nil 436} 437 438// arrayRemove is a special type in firestore. It instructs the server to remove 439// the specified values. 440type arrayRemove struct { 441 elems []interface{} 442} 443 444// ArrayRemove specifies elements to be removed from whatever array already 445// exists in the server. 446// 447// If a value exists and it's an array, values are removed from it. All 448// duplicate values are removed. 449// If a value exists and it's not an array, the value is replaced by an empty 450// array. 451// If a value does not exist, an empty array is created. 452// 453// ArrayRemove must be the value of a field directly; it cannot appear in 454// array or struct values, or in any value that is itself inside an array or 455// struct. 456func ArrayRemove(elems ...interface{}) arrayRemove { 457 return arrayRemove{elems: elems} 458} 459 460// This helper converts an arrayRemove into a proto object. 461func arrayRemoveTransform(ar arrayRemove, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) { 462 var elems []*pb.Value 463 for _, v := range ar.elems { 464 // ServerTimestamp cannot occur in an array, so we ignore transformations here. 465 pv, _, err := toProtoValue(reflect.ValueOf(v)) 466 if err != nil { 467 return nil, err 468 } 469 elems = append(elems, pv) 470 } 471 return &pb.DocumentTransform_FieldTransform{ 472 FieldPath: fp.toServiceFieldPath(), 473 TransformType: &pb.DocumentTransform_FieldTransform_RemoveAllFromArray{ 474 RemoveAllFromArray: &pb.ArrayValue{Values: elems}, 475 }, 476 }, nil 477} 478 479type transform struct { 480 t *pb.DocumentTransform_FieldTransform 481 482 // For v2 of this package, we may want to remove this field and 483 // return an error directly from the FieldTransformX functions. 484 err error 485} 486 487// FieldTransformIncrement returns a special value that can be used with Set, Create, or 488// Update that tells the server to transform the field's current value 489// by the given value. 490// 491// The supported values are: 492// 493// int, int8, int16, int32, int64 494// uint8, uint16, uint32 495// float32, float64 496// 497// If the field does not yet exist, the transformation will set the field to 498// the given value. 499func FieldTransformIncrement(n interface{}) transform { 500 v, err := numericTransformValue(n) 501 return transform{ 502 t: &pb.DocumentTransform_FieldTransform{ 503 TransformType: &pb.DocumentTransform_FieldTransform_Increment{ 504 Increment: v, 505 }, 506 }, 507 err: err, 508 } 509} 510 511// Increment is an alias for FieldTransformIncrement. 512func Increment(n interface{}) transform { 513 return FieldTransformIncrement(n) 514} 515 516// FieldTransformMaximum returns a special value that can be used with Set, Create, or 517// Update that tells the server to set the field to the maximum of the 518// field's current value and the given value. 519// 520// The supported values are: 521// 522// int, int8, int16, int32, int64 523// uint8, uint16, uint32 524// float32, float64 525// 526// If the field is not an integer or double, or if the field does not yet 527// exist, the transformation will set the field to the given value. If a 528// maximum operation is applied where the field and the input value are of 529// mixed types (that is - one is an integer and one is a double) the field 530// takes on the type of the larger operand. If the operands are equivalent 531// (e.g. 3 and 3.0), the field does not change. 0, 0.0, and -0.0 are all zero. 532// The maximum of a zero stored value and zero input value is always the 533// stored value. The maximum of any numeric value x and NaN is NaN. 534func FieldTransformMaximum(n interface{}) transform { 535 v, err := numericTransformValue(n) 536 return transform{ 537 t: &pb.DocumentTransform_FieldTransform{ 538 TransformType: &pb.DocumentTransform_FieldTransform_Maximum{ 539 Maximum: v, 540 }, 541 }, 542 err: err, 543 } 544} 545 546// FieldTransformMinimum returns a special value that can be used with Set, Create, or 547// Update that tells the server to set the field to the minimum of the 548// field's current value and the given value. 549// 550// The supported values are: 551// 552// int, int8, int16, int32, int64 553// uint8, uint16, uint32 554// float32, float64 555// 556// If the field is not an integer or double, or if the field does not yet 557// exist, the transformation will set the field to the given value. If a 558// minimum operation is applied where the field and the input value are of 559// mixed types (that is - one is an integer and one is a double) the field 560// takes on the type of the smaller operand. If the operands are equivalent 561// (e.g. 3 and 3.0), the field does not change. 0, 0.0, and -0.0 are all zero. 562// The minimum of a zero stored value and zero input value is always the 563// stored value. The minimum of any numeric value x and NaN is NaN. 564func FieldTransformMinimum(n interface{}) transform { 565 v, err := numericTransformValue(n) 566 return transform{ 567 t: &pb.DocumentTransform_FieldTransform{ 568 TransformType: &pb.DocumentTransform_FieldTransform_Minimum{ 569 Minimum: v, 570 }, 571 }, 572 err: err, 573 } 574} 575 576func numericTransformValue(n interface{}) (*pb.Value, error) { 577 switch n.(type) { 578 case int, int8, int16, int32, int64, 579 uint8, uint16, uint32, 580 float32, float64: 581 default: 582 return nil, fmt.Errorf("unsupported type %T for Increment; supported values include int, int8, int16, int32, int64, uint8, uint16, uint32, float32, float64", n) 583 } 584 585 v, _, err := toProtoValue(reflect.ValueOf(n)) 586 if err != nil { 587 return nil, err 588 } 589 return v, nil 590} 591 592func fieldTransform(ar transform, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) { 593 if ar.err != nil { 594 return nil, ar.err 595 } 596 ft := *ar.t 597 ft.FieldPath = fp.toServiceFieldPath() 598 return &ft, nil 599} 600 601type sentinel int 602 603const ( 604 // Delete is used as a value in a call to Update or Set with merge to indicate 605 // that the corresponding key should be deleted. 606 Delete sentinel = iota 607 608 // ServerTimestamp is used as a value in a call to Update to indicate that the 609 // key's value should be set to the time at which the server processed 610 // the request. 611 // 612 // ServerTimestamp must be the value of a field directly; it cannot appear in 613 // array or struct values, or in any value that is itself inside an array or 614 // struct. 615 ServerTimestamp 616) 617 618func (s sentinel) String() string { 619 switch s { 620 case Delete: 621 return "Delete" 622 case ServerTimestamp: 623 return "ServerTimestamp" 624 default: 625 return "<?sentinel?>" 626 } 627} 628 629// An Update describes an update to a value referred to by a path. 630// An Update should have either a non-empty Path or a non-empty FieldPath, 631// but not both. 632// 633// See DocumentRef.Create for acceptable values. 634// To delete a field, specify firestore.Delete as the value. 635type Update struct { 636 Path string // Will be split on dots, and must not contain any of "˜*/[]". 637 FieldPath FieldPath 638 Value interface{} 639} 640 641// An fpv is a pair of validated FieldPath and value. 642type fpv struct { 643 fieldPath FieldPath 644 value interface{} 645} 646 647func (u *Update) process() (fpv, error) { 648 if (u.Path != "") == (u.FieldPath != nil) { 649 return fpv{}, fmt.Errorf("firestore: update %+v should have exactly one of Path or FieldPath", u) 650 } 651 fp := u.FieldPath 652 var err error 653 if fp == nil { 654 fp, err = parseDotSeparatedString(u.Path) 655 if err != nil { 656 return fpv{}, err 657 } 658 } 659 if err := fp.validate(); err != nil { 660 return fpv{}, err 661 } 662 return fpv{fp, u.Value}, nil 663} 664 665// Update updates the document. The values at the given 666// field paths are replaced, but other fields of the stored document are untouched. 667func (d *DocumentRef) Update(ctx context.Context, updates []Update, preconds ...Precondition) (_ *WriteResult, err error) { 668 ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Update") 669 defer func() { trace.EndSpan(ctx, err) }() 670 671 ws, err := d.newUpdatePathWrites(updates, preconds) 672 if err != nil { 673 return nil, err 674 } 675 return d.Parent.c.commit1(ctx, ws) 676} 677 678// Collections returns an iterator over the immediate sub-collections of the document. 679func (d *DocumentRef) Collections(ctx context.Context) *CollectionIterator { 680 client := d.Parent.c 681 it := &CollectionIterator{ 682 client: client, 683 parent: d, 684 it: client.c.ListCollectionIds( 685 withResourceHeader(ctx, client.path()), 686 &pb.ListCollectionIdsRequest{Parent: d.Path}), 687 } 688 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 689 it.fetch, 690 func() int { return len(it.items) }, 691 func() interface{} { b := it.items; it.items = nil; return b }) 692 return it 693} 694 695// CollectionIterator is an iterator over sub-collections of a document. 696type CollectionIterator struct { 697 client *Client 698 parent *DocumentRef 699 it *vkit.StringIterator 700 pageInfo *iterator.PageInfo 701 nextFunc func() error 702 items []*CollectionRef 703 err error 704} 705 706// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 707func (it *CollectionIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 708 709// Next returns the next result. Its second return value is iterator.Done if there 710// are no more results. Once Next returns Done, all subsequent calls will return 711// Done. 712func (it *CollectionIterator) Next() (*CollectionRef, error) { 713 if err := it.nextFunc(); err != nil { 714 return nil, err 715 } 716 item := it.items[0] 717 it.items = it.items[1:] 718 return item, nil 719} 720 721func (it *CollectionIterator) fetch(pageSize int, pageToken string) (string, error) { 722 if it.err != nil { 723 return "", it.err 724 } 725 return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error { 726 id, err := it.it.Next() 727 if err != nil { 728 return err 729 } 730 var cr *CollectionRef 731 if it.parent == nil { 732 cr = newTopLevelCollRef(it.client, it.client.path(), id) 733 } else { 734 cr = newCollRefWithParent(it.client, it.parent, id) 735 } 736 it.items = append(it.items, cr) 737 return nil 738 }) 739} 740 741// GetAll returns all the collections remaining from the iterator. 742func (it *CollectionIterator) GetAll() ([]*CollectionRef, error) { 743 var crs []*CollectionRef 744 for { 745 cr, err := it.Next() 746 if err == iterator.Done { 747 break 748 } 749 if err != nil { 750 return nil, err 751 } 752 crs = append(crs, cr) 753 } 754 return crs, nil 755} 756 757// Common fetch code for iterators that are backed by vkit iterators. 758// TODO(jba): dedup with same function in logging/logadmin. 759func iterFetch(pageSize int, pageToken string, pi *iterator.PageInfo, next func() error) (string, error) { 760 pi.MaxSize = pageSize 761 pi.Token = pageToken 762 // Get one item, which will fill the buffer. 763 if err := next(); err != nil { 764 return "", err 765 } 766 // Collect the rest of the buffer. 767 for pi.Remaining() > 0 { 768 if err := next(); err != nil { 769 return "", err 770 } 771 } 772 return pi.Token, nil 773} 774 775// Snapshots returns an iterator over snapshots of the document. Each time the document 776// changes or is added or deleted, a new snapshot will be generated. 777func (d *DocumentRef) Snapshots(ctx context.Context) *DocumentSnapshotIterator { 778 return &DocumentSnapshotIterator{ 779 docref: d, 780 ws: newWatchStreamForDocument(ctx, d), 781 } 782} 783 784// DocumentSnapshotIterator is an iterator over snapshots of a document. 785// Call Next on the iterator to get a snapshot of the document each time it changes. 786// Call Stop on the iterator when done. 787// 788// For an example, see DocumentRef.Snapshots. 789type DocumentSnapshotIterator struct { 790 docref *DocumentRef 791 ws *watchStream 792} 793 794// Next blocks until the document changes, then returns the DocumentSnapshot for 795// the current state of the document. If the document has been deleted, Next 796// returns a DocumentSnapshot whose Exists method returns false. 797// 798// Next never returns iterator.Done unless it is called after Stop. 799func (it *DocumentSnapshotIterator) Next() (*DocumentSnapshot, error) { 800 btree, _, readTime, err := it.ws.nextSnapshot() 801 if err != nil { 802 if err == io.EOF { 803 err = iterator.Done 804 } 805 // watchStream's error is sticky, so SnapshotIterator does not need to remember it. 806 return nil, err 807 } 808 if btree.Len() == 0 { // document deleted 809 return &DocumentSnapshot{Ref: it.docref, ReadTime: readTime}, nil 810 } 811 snap, _ := btree.At(0) 812 return snap.(*DocumentSnapshot), nil 813} 814 815// Stop stops receiving snapshots. You should always call Stop when you are done with 816// a DocumentSnapshotIterator, to free up resources. It is not safe to call Stop 817// concurrently with Next. 818func (it *DocumentSnapshotIterator) Stop() { 819 it.ws.stop() 820} 821