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