docstore

package
v0.37.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2024 License: Apache-2.0 Imports: 18 Imported by: 65

Documentation

Overview

Package docstore provides a portable way of interacting with a document store. Subpackages contain driver implementations of docstore for supported services.

See https://gocloud.dev/howto/docstore/ for a detailed how-to guide.

Collections

In docstore, documents are grouped into collections, and each document has a key that is unique in its collection. You can add, retrieve, modify and delete documents by key, and you can query a collection to retrieve documents that match certain criteria.

Representing Documents

A document is a set of named fields, each with a value. A field's value can be a scalar, a list, or a nested document.

Docstore allows you to represent documents as either map[string]interface{} or struct pointers. When you represent a document as a map, the fields are map keys and the values are map values. Lists are represented with slices. For example, here is a document about a book described as a map:

doc := map[string]interface{}{
    "Title": "The Master and Margarita",
    "Author": map[string]interface{}{
        "First": "Mikhail",
        "Last": "Bulgakov",
    },
    "PublicationYears": []int{1967, 1973},
}

Note that the value of "PublicationYears" is a list, and the value of "Author" is itself a document.

Here is the same document represented with structs:

type Book struct {
    Title            string
    Author           Name
    PublicationYears []int
}

type Name struct {
    First, Last string
}

doc := &Book{
    Title: "The Master and Margarita",
    Author: Name{
        First: "Mikhail",
        Last:  "Bulgakov",
    },
    PublicationYears: []int{1967, 1973},
}

You must use a pointer to a struct to represent a document, although structs nested inside a document, like the Name struct above, need not be pointers.

Maps are best for applications where you don't know the structure of the documents. Using structs is preferred because it enforces some structure on your data.

By default, Docstore treats a struct's exported fields as the fields of the document. You can alter this default mapping by using a struct tag beginning with "docstore:". Docstore struct tags support renaming, omitting fields unconditionally, or omitting them only when they are empty, exactly like encoding/json. For example, this is the Book struct with different field names:

type Book struct {
    Title            string `docstore:"title"`
    Author           Name   `docstore:"author"`
    PublicationYears []int  `docstore:"pub_years,omitempty"`
    NumPublications  int    `docstore:"-"`
}

This struct describes a document with field names "title", "author" and "pub_years". The pub_years field is omitted from the stored document if it has length zero. The NumPublications field is never stored because it can easily be computed from the PublicationYears field.

Given a document field "Foo" and a struct type document, Docstore's decoder will look through the destination struct's field to find (in order of preference):

  • An exported field with a tag of "Foo";
  • An exported field named "Foo".

Note that unlike encoding/json, Docstore does case-sensitive matching during decoding to match the behavior of decoders in most docstore services.

Representing Data

Values stored in document fields can be any of a wide range of types. All primitive types except for complex numbers are supported, as well as slices and maps (the map key type must be a string, an integer, or a type that implements encoding.TextMarshaler). In addition, any type that implements encoding.BinaryMarshaler or encoding.TextMarshaler is permitted. This set of types closely matches the encoding/json package (see https://golang.org/pkg/encoding/json).

Times deserve special mention. Docstore can store and retrieve values of type time.Time, with two caveats. First, the timezone will not be preserved. Second, Docstore guarantees only that time.Time values are represented to millisecond precision. Many services will do better, but if you need to be sure that times are stored with nanosecond precision, convert the time.Time to another type before storing and re-create when you retrieve it. For instance, if you store Unix time in nanoseconds using time's UnixNano method, you can get the original time back (in the local timezone) with the time.Unix function.

Representing Keys

The key of a docstore document is its unique identifier, usually a field. Keys never appear alone in the docstore API, only as part of a document. For instance, to retrieve a document by key, you pass the Collection.Get method a document as a struct pointer or map with the key field populated, and docstore populates the rest of that argument with the stored contents. Docstore doesn't take zero-value key.

When you open a collection using an OpenCollection method of the service-specific driver or a URL, you specify how to extract the key from a document. Usually, you provide the name of the key field, as in the example below:

coll, err := memdocstore.OpenCollection("SSN", nil)

Here, the "SSN" field of the document is used as the key. Some drivers let you supply a function to extract the key from the document, which can be useful if the key is composed of more than one field.

Actions

Docstore supports six actions on documents as methods on the Collection type:

  • Get retrieves a document.
  • Create creates a new document.
  • Replace replaces an existing document.
  • Put puts a document into a collection, replacing it if it is already present.
  • Update applies a set of modifications to a document.
  • Delete deletes a document.

Each action acts atomically on a single document. You can execute actions individually or you can group them into an action list, like so:

err := coll.Actions().Put(doc1).Replace(doc2).Get(doc3).Do(ctx)

When you use an action list, docstore will try to optimize the execution of the actions. For example, multiple Get actions may be combined into a single "batch get" RPC. For the most part, actions in a list execute in an undefined order (perhaps concurrently) and independently, but read and write operations on the same document are executed in the user-specified order. See the documentation of ActionList for details.

Revisions

Docstore supports document revisions to distinguish different versions of a document and enable optimistic locking. By default, Docstore stores the revision in the field named "DocstoreRevision" (stored in the constant DefaultRevisionField). Providers give you the option of changing that field name.

When you pass a document with a revision field to a write action, Docstore will give it a revision at creation time or update the revision value when modifying the document. If you don't want Docstore to handle any revision logic, simply do not have the revision field in your document.

When you pass a document with a non-nil revision to Put, Replace, Update or Delete, Docstore will also compare the revision of the stored document to that of the given document before making the change. It returns an error with code FailedPrecondition on mismatch. (See https://gocloud.dev/gcerrors for information about error codes.) If modification methods are called on a document struct or map a nil revision field, then no revision checks are performed, and changes are forced blindly, but a new revision will still be given for the document. For example, if you call Get to retrieve a document with a revision, then later perform a write action with that same document, it will fail if the document was changed since the Get.

Since different services use different types for revisions, revision fields of unspecified type must be handled. When defining a document struct, define the field to be of type interface{}. For example,

type User {
    Name             string
    DocstoreRevision interface{}
}

Queries

Docstore supports querying within a collection. Call the Query method on Collection to obtain a Query value, then build your query by calling Query methods like Where, Limit and so on. Finally, call the Get method on the query to execute it. The result is an iterator, whose use is described below.

iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)

The Where method defines a filter condition, much like a WHERE clause in SQL. Conditions are of the form "field op value", where field is any document field path (including dot-separated paths), op is one of "=", ">", "<", ">=" or "<=", and value can be any value.

iter := coll.Query().Where("Author.Last", "=", "Bulgakov").Limit(3).Get(ctx)

You can make multiple Where calls. In some cases, parts of a Where clause may be processed in the driver rather than natively by the backing service, which may have performance implications for large result sets. See the driver package documentation for details.

Use the DocumentIterator returned from Query.Get by repeatedly calling its Next method until it returns io.EOF. Always call Stop when you are finished with an iterator. It is wise to use a defer statement for this.

iter := coll.Query().Where("size", ">", 10).Limit(5).Get(ctx)
defer iter.Stop()
for {
    m := map[string]interface{}{}
    err := iter.Next(ctx, m)
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    fmt.Println(m)
}

Errors

The errors returned from this package can be inspected in several ways:

The Code function from https://gocloud.dev/gcerrors will return an error code, also defined in that package, when invoked on an error.

The Collection.ErrorAs method can retrieve the underlying driver error from the returned error. See the specific driver's package doc for the supported types.

OpenCensus Integration

OpenCensus supports tracing and metric collection for multiple languages and backend providers. See https://opencensus.io.

This API collects OpenCensus traces and metrics for the following methods:

  • ActionList.Do
  • Query.Get (for the first query only; drivers may make additional calls while iterating over results)

All trace and metric names begin with the package import path. The traces add the method name. For example, "gocloud.dev/docstore/ActionList.Do". The metrics are "completed_calls", a count of completed method calls by driver, method and status (error code); and "latency", a distribution of method latency by driver and method. For example, "gocloud.dev/docstore/latency".

To enable trace collection in your application, see "Configure Exporter" at https://opencensus.io/quickstart/go/tracing. To enable metric collection in your application, see "Exporting stats" at https://opencensus.io/quickstart/go/metrics.

GORM-like Code Generation

You may find https://github.com/bartventer/docstore-gen useful.

Example (OptimisticLocking)
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"gocloud.dev/docstore/memdocstore"
	"gocloud.dev/gcerrors"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
	ctx := context.Background()

	coll, err := memdocstore.OpenCollection("Name", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer coll.Close()

	// Create a player.
	pat := &Player{Name: "Pat", Score: 7}
	if err := coll.Create(ctx, pat); err != nil {
		log.Fatal(err)
	}
	fmt.Println(pat) // memdocstore revisions are deterministic, so we can check the output.

	// Double a player's score. We cannot use Update to multiply, so we use optimistic
	// locking instead.

	// We may have to retry a few times; put a time limit on that.
	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()
	for {
		// Get the document.
		player := &Player{Name: "Pat"}
		if err := coll.Get(ctx, player); err != nil {
			log.Fatal(err)
		}
		// player.DocstoreRevision is set to the document's revision.

		// Modify the document locally.
		player.Score *= 2

		// Replace the document. player.DocstoreRevision will be checked against
		// the stored document's revision.
		err := coll.Replace(ctx, player)
		if err != nil {
			code := gcerrors.Code(err)
			// On FailedPrecondition or NotFound, try again.
			if code == gcerrors.FailedPrecondition || code == gcerrors.NotFound {
				continue
			}
			log.Fatal(err)
		}
		fmt.Println(player)
		break
	}

}
Output:

&{Pat 7 1}
&{Pat 14 2}

Index

Examples

Constants

View Source
const (
	Ascending  = "asc"
	Descending = "desc"
)

Ascending and Descending are constants for use in the OrderBy method.

View Source
const DefaultRevisionField = "DocstoreRevision"

DefaultRevisionField is the default name of the document field used for document revision information, to implement optimistic locking. See the Revisions section of the package documentation.

Variables

View Source
var NewCollection = newCollection

NewCollection is intended for use by drivers only. Do not use in application code.

View Source
var (

	// OpenCensusViews are predefined views for OpenCensus metrics.
	// The views include counts and latency distributions for API method calls.
	// See the example at https://godoc.org/go.opencensus.io/stats/view for usage.
	OpenCensusViews = oc.Views(pkgName, latencyMeasure)
)

Functions

func Increment

func Increment(amount interface{}) interface{}

Increment returns a modification that results in a field being incremented. It should only be used as a value in a Mods map, like so:

docstore.Mods{"count": docstore.Increment(1)}

The amount must be an integer or floating-point value.

Types

type Action

type Action struct {
	// contains filtered or unexported fields
}

An Action is a read or write on a single document. Use the methods of ActionList to create and execute Actions.

func (*Action) String

func (a *Action) String() string

type ActionList

type ActionList struct {
	// contains filtered or unexported fields
}

An ActionList is a group of actions that affect a single collection.

The writes in an action list (Put, Create, Replace, Update and Delete actions) must refer to distinct documents and are unordered with respect to each other. Each write happens independently of the others: all actions will be executed, even if some fail.

The Gets in an action list must also refer to distinct documents and are unordered and independent of each other.

A Get and a write may refer to the same document. Each write may be paired with only one Get in this way. The Get and write will be executed in the order specified in the list: a Get before a write will see the old value of the document; a Get after the write will see the new value if the service is strongly consistent, but may see the old value if the service is eventually consistent.

func (*ActionList) BeforeDo

func (l *ActionList) BeforeDo(f func(asFunc func(interface{}) bool) error) *ActionList

BeforeDo takes a callback function that will be called before the ActionList is executed by the underlying service. It may be invoked multiple times for a single call to ActionList.Do, because the driver may split the action list into several service calls. If any callback invocation returns an error, ActionList.Do returns an error.

The callback takes a parameter, asFunc, that converts its argument to driver-specific types. See https://gocloud.dev/concepts/as for background information.

func (*ActionList) Create

func (l *ActionList) Create(doc Document) *ActionList

Create adds an action that creates a new document to the given ActionList, and returns the ActionList. The document must not already exist; an error with code AlreadyExists is returned if it does. (See gocloud.dev/gcerrors for more on error codes.)

If the document doesn't have key fields, or the key fields are empty, meaning 0, a nil interface value, or any empty array or string, key fields with unique values will be created and doc will be populated with them if there is a way to assign those keys, see each driver for details on the requirement of generating keys.

The revision field of the document must be absent or nil.

Except for setting the revision field and possibly setting the key fields, the doc argument is not modified.

func (*ActionList) Delete

func (l *ActionList) Delete(doc Document) *ActionList

Delete adds an action that deletes a document to the given ActionList, and returns the ActionList. Only the key and revision fields of doc are used. See the Revisions section of the package documentation for how revisions are handled. If doc has no revision and the document doesn't exist, nothing happens and no error is returned.

func (*ActionList) Do

func (l *ActionList) Do(ctx context.Context) error

Do executes the action list.

If Do returns a non-nil error, it will be of type ActionListError. If any action fails, the returned error will contain the position in the ActionList of each failed action.

All the actions will be executed. Docstore tries to execute the actions as efficiently as possible. Sometimes this makes it impossible to attribute failures to specific actions; in such cases, the returned ActionListError will have entries whose Index field is negative.

func (*ActionList) Get

func (l *ActionList) Get(doc Document, fps ...FieldPath) *ActionList

Get adds an action that retrieves a document to the given ActionList, and returns the ActionList. Only the key fields of doc are used. If fps is omitted, doc will contain all the fields of the retrieved document. If fps is present, only the given field paths are retrieved. It is undefined whether other fields of doc at the time of the call are removed, unchanged, or zeroed, so for portable behavior doc should contain only the key fields. If you plan to write the document back and let Docstore to perform optimistic locking, include the revision field in fps. See more about revision at https://godoc.org/gocloud.dev/docstore#hdr-Revisions.

func (*ActionList) Put

func (l *ActionList) Put(doc Document) *ActionList

Put adds an action that adds or replaces a document to the given ActionList, and returns the ActionList. The key fields must be set.

If the revision field is non-nil, then Put behaves exactly like Replace, returning an error if the document does not exist. Otherwise, Put will create the document if it does not exist.

See the Revisions section of the package documentation for how revisions are handled.

func (*ActionList) Replace

func (l *ActionList) Replace(doc Document) *ActionList

Replace adds an action that replaces a document to the given ActionList, and returns the ActionList. The key fields of the doc argument must be set. The document must already exist; an error with code NotFound is returned if it does not (or possibly FailedPrecondition, if the doc argument has a non-nil revision). (See gocloud.dev/gcerrors for more on error codes.)

See the Revisions section of the package documentation for how revisions are handled.

func (*ActionList) String

func (l *ActionList) String() string

func (*ActionList) Update

func (l *ActionList) Update(doc Document, mods Mods) *ActionList

Update atomically applies Mods to doc, which must exist. Only the key and revision fields of doc are used. It is an error to pass an empty Mods to Update.

A modification will create a field if it doesn't exist.

No field path in mods can be a prefix of another. (It makes no sense to, say, set foo but increment foo.bar.)

See the Revisions section of the package documentation for how revisions are handled.

It is undefined whether updating a sub-field of a non-map field will succeed. For instance, if the current document is {a: 1} and Update is called with the mod "a.b": 2, then either Update will fail, or it will succeed with the result {a: {b: 2}}.

Update does not modify its doc argument, except to set the new revision. To obtain the updated document, call Get after calling Update.

type ActionListError

type ActionListError []struct {
	Index int
	Err   error
}

An ActionListError is returned by ActionList.Do. It contains all the errors encountered while executing the ActionList, and the positions of the corresponding actions.

func (ActionListError) Error

func (e ActionListError) Error() string

func (ActionListError) Unwrap

func (e ActionListError) Unwrap() error

Unwrap returns the error in e, if there is exactly one. If there is more than one error, Unwrap returns nil, since there is no way to determine which should be returned.

type Collection

type Collection struct {
	// contains filtered or unexported fields
}

A Collection represents a set of documents. It provides an easy and portable way to interact with document stores. To create a Collection, use constructors found in driver subpackages.

func OpenCollection

func OpenCollection(ctx context.Context, urlstr string) (*Collection, error)

OpenCollection opens the collection identified by the URL given. See the URLOpener documentation in driver subpackages for details on supported URL formats, and https://gocloud.dev/concepts/urls/ for more information.

Example
package main

import (
	"context"
	"log"

	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

func main() {
	ctx := context.Background()
	// Open a collection using the gcpfirestore package.
	// You will need to blank-import the package for this to work:
	//   import _ "gocloud.dev/docstore/gcpfirestore"
	coll, err := docstore.OpenCollection(ctx, "firestore://my-collection")
	if err != nil {
		log.Fatal(err)
	}
	defer coll.Close()

	_ = coll // Use the collection.
}
Output:

func (*Collection) Actions

func (c *Collection) Actions() *ActionList

Actions returns an ActionList that can be used to perform actions on the collection's documents.

Example (BulkWrite)
package main

import (
	"context"
	"log"

	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
	ctx := context.Background()
	var coll *docstore.Collection

	// Build an ActionList to create several new players, then execute it.
	// The actions may happen in any order.
	newPlayers := []string{"Pat", "Mel", "Fran"}
	actionList := coll.Actions()
	for _, p := range newPlayers {
		actionList.Create(&Player{Name: p, Score: 0})
	}
	if err := actionList.Do(ctx); err != nil {
		log.Fatal(err)
	}
}
Output:

Example (GetAfterWrite)
package main

import (
	"context"
	"fmt"
	"log"

	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
	ctx := context.Background()
	var coll *docstore.Collection

	// Add a document to the collection, then retrieve it.
	// Because both the Put and the Get refer to the same document,
	// they happen in order.
	got := Player{Name: "Pat"}
	err := coll.Actions().Put(&Player{Name: "Pat", Score: 88}).Get(&got).Do(ctx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got.Name, got.Score)
}
Output:

func (*Collection) As

func (c *Collection) As(i interface{}) bool

As converts i to driver-specific types. See https://gocloud.dev/concepts/as/ for background information, the "As" examples in this package for examples, and the driver package documentation for the specific types supported for that driver.

Example
package main

import (
	"context"
	"log"

	firestore "cloud.google.com/go/firestore/apiv1"
	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

func main() {
	// This example is specific to the gcpfirestore implementation; it demonstrates
	// access to the underlying *cloud.google.com/go/firestore/apiv1.Client.

	// You will need to blank-import the package for this to work:
	//   import _ "gocloud.dev/docstore/gcpfirestore"

	// The types exposed for As by gcpfirestore are documented in
	// https://godoc.org/gocloud.dev/docstore/gcpfirestore#hdr-As

	// This URL will open the collection using default credentials.
	ctx := context.Background()
	coll, err := docstore.OpenCollection(ctx,
		"firestore://projects/myproject/databases/(default)/documents/mycollection?name_field=myID")
	if err != nil {
		log.Fatal(err)
	}
	defer coll.Close()

	// Try to access and use the underlying mongo.Collection.
	var fsClient *firestore.Client
	if coll.As(&fsClient) {
		_ = fsClient // TODO: Use the client.
	} else {
		log.Println("Unable to access firestore.Client through Collection.As")
	}
}
Output:

func (*Collection) Close

func (c *Collection) Close() error

Close releases any resources used for the collection.

func (*Collection) Create

func (c *Collection) Create(ctx context.Context, doc Document) error

Create is a convenience for building and running a single-element action list. See ActionList.Create.

func (*Collection) Delete

func (c *Collection) Delete(ctx context.Context, doc Document) error

Delete is a convenience for building and running a single-element action list. See ActionList.Delete.

func (*Collection) ErrorAs

func (c *Collection) ErrorAs(err error, i interface{}) bool

ErrorAs converts i to driver-specific types. See https://gocloud.dev/concepts/as/ for background information and the driver package documentation for the specific types supported for that driver.

When the error is an ActionListError, ErrorAs works on individual errors in the slice, not the slice itself.

ErrorAs panics if i is nil or not a pointer. ErrorAs returns false if err == nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws/awserr"
	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

func main() {
	// This example is specific to the awsdynamodb implementation.
	// You will need to blank-import the package for this to work:
	//   import _ "gocloud.dev/docstore/awsdynamodb"

	// The types exposed for As by mongodocstore are documented in
	// https://godoc.org/gocloud.dev/docstore/mongodocstore#hdr-As

	// This URL will open the collection using default credentials.
	ctx := context.Background()
	coll, err := docstore.OpenCollection(ctx, "dynamodb://mytable?partition_key=partkey")
	if err != nil {
		log.Fatal(err)
	}
	defer coll.Close()

	doc := map[string]interface{}{"_id": "a"}
	if err := coll.Create(ctx, doc); err != nil {
		var aerr awserr.Error
		if coll.ErrorAs(err, &aerr) {
			fmt.Println("got", aerr)
		} else {
			fmt.Println("could not convert error")
		}
	}
}
Output:

func (*Collection) Get

func (c *Collection) Get(ctx context.Context, doc Document, fps ...FieldPath) error

Get is a convenience for building and running a single-element action list. See ActionList.Get.

func (*Collection) Put

func (c *Collection) Put(ctx context.Context, doc Document) error

Put is a convenience for building and running a single-element action list. See ActionList.Put.

func (*Collection) Query

func (c *Collection) Query() *Query

Query creates a new Query over the collection.

func (*Collection) Replace

func (c *Collection) Replace(ctx context.Context, doc Document) error

Replace is a convenience for building and running a single-element action list. See ActionList.Replace.

func (*Collection) RevisionToString

func (c *Collection) RevisionToString(rev interface{}) (string, error)

RevisionToString converts a document revision to a string. The returned string should be treated as opaque; its only use is to provide a serialized form that can be passed around (e.g., as a hidden field on a web form) and then turned back into a revision using StringToRevision. The string is safe for use in URLs and HTTP forms.

func (*Collection) StringToRevision

func (c *Collection) StringToRevision(s string) (interface{}, error)

StringToRevision converts a string obtained with RevisionToString to a revision.

func (*Collection) Update

func (c *Collection) Update(ctx context.Context, doc Document, mods Mods) error

Update is a convenience for building and running a single-element action list. See ActionList.Update.

Example
package main

import (
	"context"
	"log"

	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
	ctx := context.Background()
	var coll *docstore.Collection

	// Create a player.
	pat := &Player{Name: "Pat", Score: 0}
	if err := coll.Create(ctx, pat); err != nil {
		log.Fatal(err)
	}

	// Set the score to a new value.
	pat2 := &Player{Name: "Pat"}
	err := coll.Actions().Update(pat, docstore.Mods{"Score": 15}).Get(pat2).Do(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Increment the score.
	err = coll.Actions().Update(pat, docstore.Mods{"Score": docstore.Increment(5)}).Get(pat2).Do(ctx)
	if err != nil {
		log.Fatal(err)
	}
}
Output:

type CollectionURLOpener

type CollectionURLOpener interface {
	OpenCollectionURL(ctx context.Context, u *url.URL) (*Collection, error)
}

CollectionURLOpener opens a collection of documents based on a URL. The opener must not modify the URL argument. It must be safe to call from multiple goroutines.

This interface is generally implemented by types in driver packages.

type Document

type Document = interface{}

A Document is a set of field-value pairs. One or more fields, called the key fields, must uniquely identify the document in the collection. You specify the key fields when you open a collection. A field name must be a valid UTF-8 string that does not contain a '.'.

A Document can be represented as a map[string]int or a pointer to a struct. For structs, the exported fields are the document fields.

type DocumentIterator

type DocumentIterator struct {
	// contains filtered or unexported fields
}

DocumentIterator iterates over documents.

Always call Stop on the iterator.

func (*DocumentIterator) As

func (it *DocumentIterator) As(i interface{}) bool

As converts i to driver-specific types. See https://gocloud.dev/concepts/as/ for background information, the "As" examples in this package for examples, and the driver package documentation for the specific types supported for that driver.

func (*DocumentIterator) Next

func (it *DocumentIterator) Next(ctx context.Context, dst Document) error

Next stores the next document in dst. It returns io.EOF if there are no more documents. Once Next returns an error, it will always return the same error.

func (*DocumentIterator) Stop

func (it *DocumentIterator) Stop()

Stop stops the iterator. Calling Next on a stopped iterator will return io.EOF, or the error that Next previously returned.

type FieldPath

type FieldPath string

A FieldPath is a dot-separated sequence of UTF-8 field names. Examples:

room
room.size
room.size.width

A FieldPath can be used select top-level fields or elements of sub-documents. There is no way to select a single list element.

type Mods

type Mods map[FieldPath]interface{}

Mods is a map from field paths to modifications. At present, a modification is one of:

  • nil, to delete the field
  • an Increment value, to add a number to the field
  • any other value, to set the field to that value

See ActionList.Update.

type Query

type Query struct {
	// contains filtered or unexported fields
}

Query represents a query over a collection.

func (*Query) BeforeQuery

func (q *Query) BeforeQuery(f func(asFunc func(interface{}) bool) error) *Query

BeforeQuery takes a callback function that will be called before the Query is executed to the underlying service's query functionality. The callback takes a parameter, asFunc, that converts its argument to driver-specific types. See https://gocloud.dev/concepts/as/ for background information.

func (*Query) Get

func (q *Query) Get(ctx context.Context, fps ...FieldPath) *DocumentIterator

Get returns an iterator for retrieving the documents specified by the query. If field paths are provided, only those paths are set in the resulting documents.

Call Stop on the iterator when finished.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"log"

	"gocloud.dev/docstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
	ctx := context.Background()
	var coll *docstore.Collection

	// Ask for all players with scores at least 20.
	iter := coll.Query().Where("Score", ">=", 20).OrderBy("Score", docstore.Descending).Get(ctx)
	defer iter.Stop()

	// Query.Get returns an iterator. Call Next on it until io.EOF.
	for {
		var p Player
		err := iter.Next(ctx, &p)
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		} else {
			fmt.Printf("%s: %d\n", p.Name, p.Score)
		}
	}
}
Output:

Example (Full)
package main

import (
	"context"
	"fmt"
	"io"
	"log"

	"gocloud.dev/docstore"
	"gocloud.dev/docstore/memdocstore"

	_ "gocloud.dev/docstore/awsdynamodb"
	_ "gocloud.dev/docstore/gcpfirestore"
)

type Player struct {
	Name             string
	Score            int
	DocstoreRevision interface{}
}

func main() {
	ctx := context.Background()
	coll, err := memdocstore.OpenCollection("Name", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer coll.Close()

	// Add some documents to the collection.
	err = coll.Actions().
		Put(&Player{Name: "Pat", Score: 10}).
		Put(&Player{Name: "Mel", Score: 20}).
		Put(&Player{Name: "Fran", Score: 30}).
		Do(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Ask for all players with scores at least 20.
	iter := coll.Query().Where("Score", ">=", 20).OrderBy("Score", docstore.Descending).Get(ctx)
	defer iter.Stop()

	// Query.Get returns an iterator. Call Next on it until io.EOF.
	for {
		var p Player
		err := iter.Next(ctx, &p)
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		} else {
			fmt.Printf("%s: %d\n", p.Name, p.Score)
		}
	}

}
Output:

Fran: 30
Mel: 20

func (*Query) Limit

func (q *Query) Limit(n int) *Query

Limit will limit the results to at most n documents. n must be positive. It is an error to specify Limit more than once in a Get query, or at all in a Delete or Update query.

func (*Query) Offset added in v0.37.0

func (q *Query) Offset(n int) *Query

Offset (also commonly referred to as `Skip`) specifies the number of documents to skip before returning results. n must be non-negative. It is an error to specify Offset more than once in a Get query, or at all in a Delete or Update query.

func (*Query) OrderBy

func (q *Query) OrderBy(field, direction string) *Query

OrderBy specifies that the returned documents appear sorted by the given field in the given direction. A query can have at most one OrderBy clause. If it has none, the order of returned documents is unspecified. If a query has a Where clause and an OrderBy clause, the OrderBy clause's field must appear in a Where clause. It is an error to specify OrderBy in a Delete or Update query.

func (*Query) Plan

func (q *Query) Plan(fps ...FieldPath) (string, error)

Plan describes how the query would be executed if its Get method were called with the given field paths. Plan uses only information available to the client, so it cannot know whether a service uses indexes or scans internally.

func (*Query) Where

func (q *Query) Where(fp FieldPath, op string, value interface{}) *Query

Where expresses a condition on the query. Valid ops are: "=", ">", "<", ">=", "<=, "in", "not-in". Valid values are strings, integers, floating-point numbers, and time.Time values.

type URLMux

type URLMux struct {
	// contains filtered or unexported fields
}

URLMux is a URL opener multiplexer. It matches the scheme of the URLs against a set of registered schemes and calls the opener that matches the URL's scheme. See https://gocloud.dev/concepts/urls/ for more information.

The zero value is a multiplexer with no registered scheme.

func DefaultURLMux

func DefaultURLMux() *URLMux

DefaultURLMux returns the URLMux used by OpenCollection.

Driver packages can use this to register their CollectionURLOpener on the mux.

func (*URLMux) CollectionSchemes

func (mux *URLMux) CollectionSchemes() []string

CollectionSchemes returns a sorted slice of the registered Collection schemes.

func (*URLMux) OpenCollection

func (mux *URLMux) OpenCollection(ctx context.Context, urlstr string) (*Collection, error)

OpenCollection calls OpenCollectionURL with the URL parsed from urlstr. OpenCollection is safe to call from multiple goroutines.

func (*URLMux) OpenCollectionURL

func (mux *URLMux) OpenCollectionURL(ctx context.Context, u *url.URL) (*Collection, error)

OpenCollectionURL dispatches the URL to the opener that is registered with the URL's scheme. OpenCollectionURL is safe to call from multiple goroutines.

func (*URLMux) RegisterCollection

func (mux *URLMux) RegisterCollection(scheme string, opener CollectionURLOpener)

RegisterCollection registers the opener with the given scheme. If an opener already exists for the scheme, RegisterCollection panics.

func (*URLMux) ValidCollectionScheme

func (mux *URLMux) ValidCollectionScheme(scheme string) bool

ValidCollectionScheme returns true iff scheme has been registered for Collections.

Directories

Path Synopsis
Package awsdynamodb provides a docstore implementation backed by Amazon DynamoDB.
Package awsdynamodb provides a docstore implementation backed by Amazon DynamoDB.
Package driver defines interfaces to be implemented by docstore drivers, which will be used by the docstore package to interact with the underlying services.
Package driver defines interfaces to be implemented by docstore drivers, which will be used by the docstore package to interact with the underlying services.
Package drivertest provides a conformance test for implementations of driver.
Package drivertest provides a conformance test for implementations of driver.
Package gcpfirestore provides a docstore implementation backed by Google Cloud Firestore.
Package gcpfirestore provides a docstore implementation backed by Google Cloud Firestore.
internal
fields
Package fields provides a view of the fields of a struct that follows the Go rules, amended to consider tags and case insensitivity.
Package fields provides a view of the fields of a struct that follows the Go rules, amended to consider tags and case insensitivity.
Package memdocstore provides an in-process in-memory implementation of the docstore API.
Package memdocstore provides an in-process in-memory implementation of the docstore API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL