1// Copyright 2019 The Go Cloud Development Kit Authors 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// https://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 docstore 16 17import ( 18 "context" 19 "encoding/base64" 20 "fmt" 21 "log" 22 "reflect" 23 "runtime" 24 "sort" 25 "strings" 26 "sync" 27 "unicode/utf8" 28 29 "gocloud.dev/docstore/driver" 30 "gocloud.dev/gcerrors" 31 "gocloud.dev/internal/gcerr" 32 "gocloud.dev/internal/oc" 33) 34 35// A Document is a set of field-value pairs. One or more fields, called the key 36// fields, must uniquely identify the document in the collection. You specify the key 37// fields when you open a collection. 38// A field name must be a valid UTF-8 string that does not contain a '.'. 39// 40// A Document can be represented as a map[string]int or a pointer to a struct. For 41// structs, the exported fields are the document fields. 42type Document = interface{} 43 44// A Collection represents a set of documents. It provides an easy and portable 45// way to interact with document stores. 46// To create a Collection, use constructors found in driver subpackages. 47type Collection struct { 48 driver driver.Collection 49 tracer *oc.Tracer 50 mu sync.Mutex 51 closed bool 52} 53 54const pkgName = "gocloud.dev/docstore" 55 56var ( 57 latencyMeasure = oc.LatencyMeasure(pkgName) 58 59 // OpenCensusViews are predefined views for OpenCensus metrics. 60 // The views include counts and latency distributions for API method calls. 61 // See the example at https://godoc.org/go.opencensus.io/stats/view for usage. 62 OpenCensusViews = oc.Views(pkgName, latencyMeasure) 63) 64 65// NewCollection is intended for use by drivers only. Do not use in application code. 66var NewCollection = newCollection 67 68// newCollection makes a Collection. 69func newCollection(d driver.Collection) *Collection { 70 c := &Collection{ 71 driver: d, 72 tracer: &oc.Tracer{ 73 Package: pkgName, 74 Provider: oc.ProviderName(d), 75 LatencyMeasure: latencyMeasure, 76 }, 77 } 78 _, file, lineno, ok := runtime.Caller(1) 79 runtime.SetFinalizer(c, func(c *Collection) { 80 c.mu.Lock() 81 closed := c.closed 82 c.mu.Unlock() 83 if !closed { 84 var caller string 85 if ok { 86 caller = fmt.Sprintf(" (%s:%d)", file, lineno) 87 } 88 log.Printf("A docstore.Collection was never closed%s", caller) 89 } 90 }) 91 return c 92} 93 94// DefaultRevisionField is the default name of the document field used for document revision 95// information, to implement optimistic locking. 96// See the Revisions section of the package documentation. 97const DefaultRevisionField = "DocstoreRevision" 98 99func (c *Collection) revisionField() string { 100 if r := c.driver.RevisionField(); r != "" { 101 return r 102 } 103 return DefaultRevisionField 104} 105 106// A FieldPath is a dot-separated sequence of UTF-8 field names. Examples: 107// room 108// room.size 109// room.size.width 110// 111// A FieldPath can be used select top-level fields or elements of sub-documents. 112// There is no way to select a single list element. 113type FieldPath string 114 115// Actions returns an ActionList that can be used to perform 116// actions on the collection's documents. 117func (c *Collection) Actions() *ActionList { 118 return &ActionList{coll: c} 119} 120 121// An ActionList is a group of actions that affect a single collection. 122// 123// The writes in an action list (Put, Create, Replace, Update and Delete actions) 124// must refer to distinct documents and are unordered with respect to each other. 125// Each write happens independently of the others: all actions will be executed, even 126// if some fail. 127// 128// The Gets in an action list must also refer to distinct documents and are unordered 129// and independent of each other. 130// 131// A Get and a write may refer to the same document. Each write may be paired with 132// only one Get in this way. The Get and write will be executed in the order 133// specified in the list: a Get before a write will see the old value of the 134// document; a Get after the write will see the new value if the service is strongly 135// consistent, but may see the old value if the service is eventually consistent. 136type ActionList struct { 137 coll *Collection 138 actions []*Action 139 beforeDo func(asFunc func(interface{}) bool) error 140} 141 142// An Action is a read or write on a single document. 143// Use the methods of ActionList to create and execute Actions. 144type Action struct { 145 kind driver.ActionKind 146 doc Document 147 fieldpaths []FieldPath // paths to retrieve, for Get 148 mods Mods // modifications to make, for Update 149} 150 151func (l *ActionList) add(a *Action) *ActionList { 152 l.actions = append(l.actions, a) 153 return l 154} 155 156// Create adds an action that creates a new document to the given ActionList, and 157// returns the ActionList. The document must not already exist; an error with code 158// AlreadyExists is returned if it does. (See gocloud.dev/gcerrors for more on error 159// codes.) 160// 161// If the document doesn't have key fields, or the key fields are empty, meaning 162// 0, a nil interface value, or any empty array or string, key fields with 163// unique values will be created and doc will be populated with them if there is 164// a way to assign those keys, see each driver for details on the requirement of 165// generating keys. 166// 167// The revision field of the document must be absent or nil. 168// 169// Except for setting the revision field and possibly setting the key fields, the doc 170// argument is not modified. 171func (l *ActionList) Create(doc Document) *ActionList { 172 return l.add(&Action{kind: driver.Create, doc: doc}) 173} 174 175// Replace adds an action that replaces a document to the given ActionList, and 176// returns the ActionList. The key fields of the doc argument must be set. The 177// document must already exist; an error with code NotFound is returned if it does 178// not (or possibly FailedPrecondition, if the doc argument has a non-nil revision). 179// (See gocloud.dev/gcerrors for more on error codes.) 180// 181// See the Revisions section of the package documentation for how revisions are 182// handled. 183func (l *ActionList) Replace(doc Document) *ActionList { 184 return l.add(&Action{kind: driver.Replace, doc: doc}) 185} 186 187// Put adds an action that adds or replaces a document to the given ActionList, and returns the ActionList. 188// The key fields must be set. 189// 190// If the revision field is non-nil, then Put behaves exactly like Replace, returning 191// an error if the document does not exist. Otherwise, Put will create the document 192// if it does not exist. 193// 194// See the Revisions section of the package documentation for how revisions are 195// handled. 196func (l *ActionList) Put(doc Document) *ActionList { 197 return l.add(&Action{kind: driver.Put, doc: doc}) 198} 199 200// Delete adds an action that deletes a document to the given ActionList, and returns 201// the ActionList. Only the key and revision fields of doc are used. 202// See the Revisions section of the package documentation for how revisions are 203// handled. 204// If doc has no revision and the document doesn't exist, nothing happens and no 205// error is returned. 206func (l *ActionList) Delete(doc Document) *ActionList { 207 // Rationale for not returning an error if the document does not exist: 208 // Returning an error might be informative and could be ignored, but if the 209 // semantics of an action list are to stop at first error, then we might abort a 210 // list of Deletes just because one of the docs was not present, and that seems 211 // wrong, or at least something you'd want to turn off. 212 return l.add(&Action{kind: driver.Delete, doc: doc}) 213} 214 215// Get adds an action that retrieves a document to the given ActionList, and 216// returns the ActionList. 217// Only the key fields of doc are used. 218// If fps is omitted, doc will contain all the fields of the retrieved document. 219// If fps is present, only the given field paths are retrieved. It is undefined 220// whether other fields of doc at the time of the call are removed, unchanged, 221// or zeroed, so for portable behavior doc should contain only the key fields. 222// If you plan to write the document back and let Docstore to perform optimistic 223// locking, include the revision field in fps. See more about revision at 224// https://godoc.org/gocloud.dev/docstore#hdr-Revisions. 225func (l *ActionList) Get(doc Document, fps ...FieldPath) *ActionList { 226 return l.add(&Action{ 227 kind: driver.Get, 228 doc: doc, 229 fieldpaths: fps, 230 }) 231} 232 233// Update atomically applies Mods to doc, which must exist. 234// Only the key and revision fields of doc are used. 235// It is an error to pass an empty Mods to Update. 236// 237// A modification will create a field if it doesn't exist. 238// 239// No field path in mods can be a prefix of another. (It makes no sense 240// to, say, set foo but increment foo.bar.) 241// 242// See the Revisions section of the package documentation for how revisions are 243// handled. 244// 245// It is undefined whether updating a sub-field of a non-map field will succeed. 246// For instance, if the current document is {a: 1} and Update is called with the 247// mod "a.b": 2, then either Update will fail, or it will succeed with the result 248// {a: {b: 2}}. 249// 250// Update does not modify its doc argument, except to set the new revision. To obtain 251// the updated document, call Get after calling Update. 252func (l *ActionList) Update(doc Document, mods Mods) *ActionList { 253 return l.add(&Action{ 254 kind: driver.Update, 255 doc: doc, 256 mods: mods, 257 }) 258} 259 260// Mods is a map from field paths to modifications. 261// At present, a modification is one of: 262// - nil, to delete the field 263// - an Increment value, to add a number to the field 264// - any other value, to set the field to that value 265// See ActionList.Update. 266type Mods map[FieldPath]interface{} 267 268// Increment returns a modification that results in a field being incremented. It 269// should only be used as a value in a Mods map, like so: 270// 271// docstore.Mods{"count": docstore.Increment(1)} 272// 273// The amount must be an integer or floating-point value. 274func Increment(amount interface{}) interface{} { 275 return driver.IncOp{amount} 276} 277 278// An ActionListError is returned by ActionList.Do. It contains all the errors 279// encountered while executing the ActionList, and the positions of the corresponding 280// actions. 281type ActionListError []struct { 282 Index int 283 Err error 284} 285 286// TODO(jba): use xerrors formatting. 287 288func (e ActionListError) Error() string { 289 var s []string 290 for _, x := range e { 291 s = append(s, fmt.Sprintf("at %d: %v", x.Index, x.Err)) 292 } 293 return strings.Join(s, "; ") 294} 295 296// Unwrap returns the error in e, if there is exactly one. If there is more than one 297// error, Unwrap returns nil, since there is no way to determine which should be 298// returned. 299func (e ActionListError) Unwrap() error { 300 if len(e) == 1 { 301 return e[0].Err 302 } 303 // Return nil when e is nil, or has more than one error. 304 // When there are multiple errors, it doesn't make sense to return any of them. 305 return nil 306} 307 308// BeforeDo takes a callback function that will be called before the ActionList is 309// executed by the underlying service. It may be invoked multiple times for a single 310// call to ActionList.Do, because the driver may split the action list into several 311// service calls. If any callback invocation returns an error, ActionList.Do returns 312// an error. 313// 314// The callback takes a parameter, asFunc, that converts its argument to 315// driver-specific types. See https://gocloud.dev/concepts/as for background 316// information. 317func (l *ActionList) BeforeDo(f func(asFunc func(interface{}) bool) error) *ActionList { 318 l.beforeDo = f 319 return l 320} 321 322// Do executes the action list. 323// 324// If Do returns a non-nil error, it will be of type ActionListError. If any action 325// fails, the returned error will contain the position in the ActionList of each 326// failed action. 327// 328// All the actions will be executed. Docstore tries to execute the actions as 329// efficiently as possible. Sometimes this makes it impossible to attribute failures 330// to specific actions; in such cases, the returned ActionListError will have entries 331// whose Index field is negative. 332func (l *ActionList) Do(ctx context.Context) error { 333 return l.do(ctx, true) 334} 335 336// do implements Do with optional OpenCensus tracing, so it can be used internally. 337func (l *ActionList) do(ctx context.Context, oc bool) (err error) { 338 if err := l.coll.checkClosed(); err != nil { 339 return ActionListError{{-1, errClosed}} 340 } 341 342 if oc { 343 ctx = l.coll.tracer.Start(ctx, "ActionList.Do") 344 defer func() { l.coll.tracer.End(ctx, err) }() 345 } 346 347 das, err := l.toDriverActions() 348 if err != nil { 349 return err 350 } 351 dopts := &driver.RunActionsOptions{BeforeDo: l.beforeDo} 352 alerr := ActionListError(l.coll.driver.RunActions(ctx, das, dopts)) 353 if len(alerr) == 0 { 354 return nil // Explicitly return nil, because alerr is not of type error. 355 } 356 for i := range alerr { 357 alerr[i].Err = wrapError(l.coll.driver, alerr[i].Err) 358 } 359 return alerr 360} 361 362func (l *ActionList) toDriverActions() ([]*driver.Action, error) { 363 var das []*driver.Action 364 var alerr ActionListError 365 // Create a set of (document key, is Get action) pairs for detecting duplicates: 366 // an action list can have at most one get and at most one write for each key. 367 type keyAndKind struct { 368 key interface{} 369 isGet bool 370 } 371 seen := map[keyAndKind]bool{} 372 for i, a := range l.actions { 373 d, err := l.coll.toDriverAction(a) 374 // Check for duplicate key. 375 if err == nil && d.Key != nil { 376 kk := keyAndKind{d.Key, d.Kind == driver.Get} 377 if seen[kk] { 378 err = gcerr.Newf(gcerr.InvalidArgument, nil, "duplicate key in action list: %v", d.Key) 379 } else { 380 seen[kk] = true 381 } 382 } 383 if err != nil { 384 alerr = append(alerr, struct { 385 Index int 386 Err error 387 }{i, wrapError(l.coll.driver, err)}) 388 } else { 389 d.Index = i 390 das = append(das, d) 391 } 392 } 393 if len(alerr) > 0 { 394 return nil, alerr 395 } 396 return das, nil 397} 398 399func (c *Collection) toDriverAction(a *Action) (*driver.Action, error) { 400 ddoc, err := driver.NewDocument(a.doc) 401 if err != nil { 402 return nil, err 403 } 404 key, err := c.driver.Key(ddoc) 405 if err != nil { 406 if gcerrors.Code(err) != gcerr.InvalidArgument { 407 err = gcerr.Newf(gcerr.InvalidArgument, err, "bad document key") 408 } 409 return nil, err 410 } 411 if key == nil || driver.IsEmptyValue(reflect.ValueOf(key)) { 412 if a.kind != driver.Create { 413 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "missing document key") 414 } 415 // set the key to nil so that the following code does not need to check for 416 // empty. 417 key = nil 418 } 419 if reflect.ValueOf(key).Kind() == reflect.Ptr { 420 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "keys cannot be pointers") 421 } 422 rev, _ := ddoc.GetField(c.revisionField()) 423 if a.kind == driver.Create && rev != nil { 424 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "cannot create a document with a revision field") 425 } 426 kind := a.kind 427 if kind == driver.Put && rev != nil { 428 // A Put with a revision field is equivalent to a Replace. 429 kind = driver.Replace 430 } 431 d := &driver.Action{Kind: kind, Doc: ddoc, Key: key} 432 if a.fieldpaths != nil { 433 d.FieldPaths, err = parseFieldPaths(a.fieldpaths) 434 if err != nil { 435 return nil, err 436 } 437 } 438 if a.kind == driver.Update { 439 d.Mods, err = toDriverMods(a.mods) 440 if err != nil { 441 return nil, err 442 } 443 } 444 return d, nil 445} 446 447func parseFieldPaths(fps []FieldPath) ([][]string, error) { 448 res := make([][]string, len(fps)) 449 for i, s := range fps { 450 fp, err := parseFieldPath(s) 451 if err != nil { 452 return nil, err 453 } 454 res[i] = fp 455 } 456 return res, nil 457} 458 459func toDriverMods(mods Mods) ([]driver.Mod, error) { 460 // Convert mods from a map to a slice of (fieldPath, value) pairs. 461 // The map is easier for users to write, but the slice is easier 462 // to process. 463 if len(mods) == 0 { 464 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "no mods passed to Update") 465 } 466 467 // Sort keys so tests are deterministic. 468 // After sorting, a key might not immediately follow its prefix. Consider the 469 // sorted list of keys "a", "a+b", "a.b". "a" is prefix of "a.b", but since '+' 470 // sorts before '.', it is not adjacent to it. All we can assume is that the 471 // prefix is before the key. 472 var keys []string 473 for k := range mods { 474 keys = append(keys, string(k)) 475 } 476 sort.Strings(keys) 477 478 var dmods []driver.Mod 479 for _, k := range keys { 480 k := FieldPath(k) 481 v := mods[k] 482 fp, err := parseFieldPath(k) 483 if err != nil { 484 return nil, err 485 } 486 for _, d := range dmods { 487 if fpHasPrefix(fp, d.FieldPath) { 488 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, 489 "field path %q is a prefix of %q", strings.Join(d.FieldPath, "."), k) 490 } 491 } 492 if inc, ok := v.(driver.IncOp); ok && !isIncNumber(inc.Amount) { 493 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, 494 "Increment amount %v of type %[1]T must be an integer or floating-point number", inc.Amount) 495 } 496 dmods = append(dmods, driver.Mod{FieldPath: fp, Value: v}) 497 } 498 return dmods, nil 499} 500 501// fpHasPrefix reports whether the field path fp begins with prefix. 502func fpHasPrefix(fp, prefix []string) bool { 503 if len(fp) < len(prefix) { 504 return false 505 } 506 for i, p := range prefix { 507 if fp[i] != p { 508 return false 509 } 510 } 511 return true 512} 513 514func isIncNumber(x interface{}) bool { 515 switch reflect.TypeOf(x).Kind() { 516 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 517 return true 518 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 519 return true 520 case reflect.Float32, reflect.Float64: 521 return true 522 default: 523 return false 524 } 525} 526 527func (l *ActionList) String() string { 528 var as []string 529 for _, a := range l.actions { 530 as = append(as, a.String()) 531 } 532 return "[" + strings.Join(as, ", ") + "]" 533} 534 535func (a *Action) String() string { 536 buf := &strings.Builder{} 537 fmt.Fprintf(buf, "%s(%v", a.kind, a.doc) 538 for _, fp := range a.fieldpaths { 539 fmt.Fprintf(buf, ", %s", fp) 540 } 541 for _, m := range a.mods { 542 fmt.Fprintf(buf, ", %v", m) 543 } 544 fmt.Fprint(buf, ")") 545 return buf.String() 546} 547 548// Create is a convenience for building and running a single-element action list. 549// See ActionList.Create. 550func (c *Collection) Create(ctx context.Context, doc Document) error { 551 if err := c.Actions().Create(doc).Do(ctx); err != nil { 552 return err.(ActionListError).Unwrap() 553 } 554 return nil 555} 556 557// Replace is a convenience for building and running a single-element action list. 558// See ActionList.Replace. 559func (c *Collection) Replace(ctx context.Context, doc Document) error { 560 if err := c.Actions().Replace(doc).Do(ctx); err != nil { 561 return err.(ActionListError).Unwrap() 562 } 563 return nil 564} 565 566// Put is a convenience for building and running a single-element action list. 567// See ActionList.Put. 568func (c *Collection) Put(ctx context.Context, doc Document) error { 569 if err := c.Actions().Put(doc).Do(ctx); err != nil { 570 return err.(ActionListError).Unwrap() 571 } 572 return nil 573} 574 575// Delete is a convenience for building and running a single-element action list. 576// See ActionList.Delete. 577func (c *Collection) Delete(ctx context.Context, doc Document) error { 578 if err := c.Actions().Delete(doc).Do(ctx); err != nil { 579 return err.(ActionListError).Unwrap() 580 } 581 return nil 582} 583 584// Get is a convenience for building and running a single-element action list. 585// See ActionList.Get. 586func (c *Collection) Get(ctx context.Context, doc Document, fps ...FieldPath) error { 587 if err := c.Actions().Get(doc, fps...).Do(ctx); err != nil { 588 return err.(ActionListError).Unwrap() 589 } 590 return nil 591} 592 593// Update is a convenience for building and running a single-element action list. 594// See ActionList.Update. 595func (c *Collection) Update(ctx context.Context, doc Document, mods Mods) error { 596 if err := c.Actions().Update(doc, mods).Do(ctx); err != nil { 597 return err.(ActionListError).Unwrap() 598 } 599 return nil 600} 601 602func parseFieldPath(fp FieldPath) ([]string, error) { 603 if len(fp) == 0 { 604 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "empty field path") 605 } 606 if !utf8.ValidString(string(fp)) { 607 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "invalid UTF-8 field path %q", fp) 608 } 609 parts := strings.Split(string(fp), ".") 610 for _, p := range parts { 611 if p == "" { 612 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "empty component in field path %q", fp) 613 } 614 } 615 return parts, nil 616} 617 618// RevisionToString converts a document revision to a string. The returned 619// string should be treated as opaque; its only use is to provide a serialized 620// form that can be passed around (e.g., as a hidden field on a web form) 621// and then turned back into a revision using StringToRevision. The string is safe 622// for use in URLs and HTTP forms. 623func (c *Collection) RevisionToString(rev interface{}) (string, error) { 624 if rev == nil { 625 return "", gcerr.Newf(gcerr.InvalidArgument, nil, "RevisionToString: nil revision") 626 } 627 bytes, err := c.driver.RevisionToBytes(rev) 628 if err != nil { 629 return "", wrapError(c.driver, err) 630 } 631 return base64.RawURLEncoding.EncodeToString(bytes), nil 632} 633 634// StringToRevision converts a string obtained with RevisionToString 635// to a revision. 636func (c *Collection) StringToRevision(s string) (interface{}, error) { 637 if s == "" { 638 return "", gcerr.Newf(gcerr.InvalidArgument, nil, "StringToRevision: empty string") 639 } 640 bytes, err := base64.RawURLEncoding.DecodeString(s) 641 if err != nil { 642 return nil, err 643 } 644 rev, err := c.driver.BytesToRevision(bytes) 645 if err != nil { 646 return "", wrapError(c.driver, err) 647 } 648 return rev, nil 649} 650 651// As converts i to driver-specific types. 652// See https://gocloud.dev/concepts/as/ for background information, the "As" 653// examples in this package for examples, and the driver package 654// documentation for the specific types supported for that driver. 655func (c *Collection) As(i interface{}) bool { 656 if i == nil { 657 return false 658 } 659 return c.driver.As(i) 660} 661 662var errClosed = gcerr.Newf(gcerr.FailedPrecondition, nil, "docstore: Collection has been closed") 663 664// Close releases any resources used for the collection. 665func (c *Collection) Close() error { 666 c.mu.Lock() 667 prev := c.closed 668 c.closed = true 669 c.mu.Unlock() 670 if prev { 671 return errClosed 672 } 673 return wrapError(c.driver, c.driver.Close()) 674} 675 676func (c *Collection) checkClosed() error { 677 c.mu.Lock() 678 defer c.mu.Unlock() 679 if c.closed { 680 return errClosed 681 } 682 return nil 683} 684 685func wrapError(c driver.Collection, err error) error { 686 if err == nil { 687 return nil 688 } 689 if gcerr.DoNotWrap(err) { 690 return err 691 } 692 if _, ok := err.(*gcerr.Error); ok { 693 return err 694 } 695 return gcerr.New(c.ErrorCode(err), err, 2, "docstore") 696} 697 698// ErrorAs converts i to driver-specific types. See 699// https://gocloud.dev/concepts/as/ for background information and the 700// driver package documentation for the specific types supported for 701// that driver. 702// 703// When the error is an ActionListError, ErrorAs works on individual errors in 704// the slice, not the slice itself. 705// 706// ErrorAs panics if i is nil or not a pointer. 707// ErrorAs returns false if err == nil. 708func (c *Collection) ErrorAs(err error, i interface{}) bool { 709 return gcerr.ErrorAs(err, i, c.driver.ErrorAs) 710} 711