Upadte vendored dependencies

This commit is contained in:
Alexander Neumann
2017-10-22 10:07:36 +02:00
parent 315b7f282f
commit 8d37b723ca
380 changed files with 136541 additions and 78532 deletions

44
vendor/cloud.google.com/go/firestore/apiv1beta1/doc.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// AUTO-GENERATED CODE. DO NOT EDIT.
// Package firestore is an auto-generated package for the
// Google Cloud Firestore API.
//
// NOTE: This package is in beta. It is not stable, and may be subject to changes.
//
//
// Use the client at cloud.google.com/go/firestore in preference to this.
package firestore
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
func insertXGoog(ctx context.Context, val []string) context.Context {
md, _ := metadata.FromOutgoingContext(ctx)
md = md.Copy()
md["x-goog-api-client"] = val
return metadata.NewOutgoingContext(ctx, md)
}
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
func DefaultAuthScopes() []string {
return []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/datastore",
}
}

View File

@@ -0,0 +1,544 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// AUTO-GENERATED CODE. DO NOT EDIT.
package firestore
import (
"math"
"time"
"cloud.google.com/go/internal/version"
firestorepb "google.golang.org/genproto/googleapis/firestore/v1beta1"
gax "github.com/googleapis/gax-go"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// CallOptions contains the retry settings for each method of Client.
type CallOptions struct {
GetDocument []gax.CallOption
ListDocuments []gax.CallOption
CreateDocument []gax.CallOption
UpdateDocument []gax.CallOption
DeleteDocument []gax.CallOption
BatchGetDocuments []gax.CallOption
BeginTransaction []gax.CallOption
Commit []gax.CallOption
Rollback []gax.CallOption
RunQuery []gax.CallOption
Write []gax.CallOption
Listen []gax.CallOption
ListCollectionIds []gax.CallOption
}
func defaultClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("firestore.googleapis.com:443"),
option.WithScopes(DefaultAuthScopes()...),
}
}
func defaultCallOptions() *CallOptions {
retry := map[[2]string][]gax.CallOption{
{"default", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 100 * time.Millisecond,
Max: 60000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
{"streaming", "idempotent"}: {
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 100 * time.Millisecond,
Max: 60000 * time.Millisecond,
Multiplier: 1.3,
})
}),
},
}
return &CallOptions{
GetDocument: retry[[2]string{"default", "idempotent"}],
ListDocuments: retry[[2]string{"default", "idempotent"}],
CreateDocument: retry[[2]string{"default", "non_idempotent"}],
UpdateDocument: retry[[2]string{"default", "non_idempotent"}],
DeleteDocument: retry[[2]string{"default", "idempotent"}],
BatchGetDocuments: retry[[2]string{"streaming", "idempotent"}],
BeginTransaction: retry[[2]string{"default", "idempotent"}],
Commit: retry[[2]string{"default", "non_idempotent"}],
Rollback: retry[[2]string{"default", "idempotent"}],
RunQuery: retry[[2]string{"default", "idempotent"}],
Write: retry[[2]string{"streaming", "non_idempotent"}],
Listen: retry[[2]string{"streaming", "idempotent"}],
ListCollectionIds: retry[[2]string{"default", "idempotent"}],
}
}
// Client is a client for interacting with Google Cloud Firestore API.
type Client struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
client firestorepb.FirestoreClient
// The call options for this service.
CallOptions *CallOptions
// The metadata to be sent with each request.
xGoogHeader []string
}
// NewClient creates a new firestore client.
//
// The Cloud Firestore service.
//
// This service exposes several types of comparable timestamps:
//
// create_time - The time at which a document was created. Changes only
// when a document is deleted, then re-created. Increases in a strict
// monotonic fashion.
//
// update_time - The time at which a document was last updated. Changes
// every time a document is modified. Does not change when a write results
// in no modifications. Increases in a strict monotonic fashion.
//
// read_time - The time at which a particular state was observed. Used
// to denote a consistent snapshot of the database or the time at which a
// Document was observed to not exist.
//
// commit_time - The time at which the writes in a transaction were
// committed. Any read with an equal or greater read_time is guaranteed
// to see the effects of the transaction.
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
conn, err := transport.DialGRPC(ctx, append(defaultClientOptions(), opts...)...)
if err != nil {
return nil, err
}
c := &Client{
conn: conn,
CallOptions: defaultCallOptions(),
client: firestorepb.NewFirestoreClient(conn),
}
c.SetGoogleClientInfo()
return c, nil
}
// Connection returns the client's connection to the API service.
func (c *Client) Connection() *grpc.ClientConn {
return c.conn
}
// Close closes the connection to the API service. The user should invoke this when
// the client is no longer required.
func (c *Client) Close() error {
return c.conn.Close()
}
// SetGoogleClientInfo sets the name and version of the application in
// the `x-goog-api-client` header passed on each request. Intended for
// use by Google-written clients.
func (c *Client) SetGoogleClientInfo(keyval ...string) {
kv := append([]string{"gl-go", version.Go()}, keyval...)
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
c.xGoogHeader = []string{gax.XGoogHeader(kv...)}
}
// DatabaseRootPath returns the path for the database root resource.
func DatabaseRootPath(project, database string) string {
return "" +
"projects/" +
project +
"/databases/" +
database +
""
}
// DocumentRootPath returns the path for the document root resource.
func DocumentRootPath(project, database string) string {
return "" +
"projects/" +
project +
"/databases/" +
database +
"/documents" +
""
}
// DocumentPathPath returns the path for the document path resource.
func DocumentPathPath(project, database, documentPath string) string {
return "" +
"projects/" +
project +
"/databases/" +
database +
"/documents/" +
documentPath +
""
}
// AnyPathPath returns the path for the any path resource.
func AnyPathPath(project, database, document, anyPath string) string {
return "" +
"projects/" +
project +
"/databases/" +
database +
"/documents/" +
document +
"/" +
anyPath +
""
}
// GetDocument gets a single document.
func (c *Client) GetDocument(ctx context.Context, req *firestorepb.GetDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.GetDocument[0:len(c.CallOptions.GetDocument):len(c.CallOptions.GetDocument)], opts...)
var resp *firestorepb.Document
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetDocument(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListDocuments lists documents.
func (c *Client) ListDocuments(ctx context.Context, req *firestorepb.ListDocumentsRequest, opts ...gax.CallOption) *DocumentIterator {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.ListDocuments[0:len(c.CallOptions.ListDocuments):len(c.CallOptions.ListDocuments)], opts...)
it := &DocumentIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*firestorepb.Document, string, error) {
var resp *firestorepb.ListDocumentsResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ListDocuments(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Documents, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
return it
}
// CreateDocument creates a new document.
func (c *Client) CreateDocument(ctx context.Context, req *firestorepb.CreateDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.CreateDocument[0:len(c.CallOptions.CreateDocument):len(c.CallOptions.CreateDocument)], opts...)
var resp *firestorepb.Document
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateDocument(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateDocument updates or inserts a document.
func (c *Client) UpdateDocument(ctx context.Context, req *firestorepb.UpdateDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.UpdateDocument[0:len(c.CallOptions.UpdateDocument):len(c.CallOptions.UpdateDocument)], opts...)
var resp *firestorepb.Document
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.UpdateDocument(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DeleteDocument deletes a document.
func (c *Client) DeleteDocument(ctx context.Context, req *firestorepb.DeleteDocumentRequest, opts ...gax.CallOption) error {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.DeleteDocument[0:len(c.CallOptions.DeleteDocument):len(c.CallOptions.DeleteDocument)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteDocument(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// BatchGetDocuments gets multiple documents.
//
// Documents returned by this method are not guaranteed to be returned in the
// same order that they were requested.
func (c *Client) BatchGetDocuments(ctx context.Context, req *firestorepb.BatchGetDocumentsRequest, opts ...gax.CallOption) (firestorepb.Firestore_BatchGetDocumentsClient, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.BatchGetDocuments[0:len(c.CallOptions.BatchGetDocuments):len(c.CallOptions.BatchGetDocuments)], opts...)
var resp firestorepb.Firestore_BatchGetDocumentsClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.BatchGetDocuments(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// BeginTransaction starts a new transaction.
func (c *Client) BeginTransaction(ctx context.Context, req *firestorepb.BeginTransactionRequest, opts ...gax.CallOption) (*firestorepb.BeginTransactionResponse, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.BeginTransaction[0:len(c.CallOptions.BeginTransaction):len(c.CallOptions.BeginTransaction)], opts...)
var resp *firestorepb.BeginTransactionResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.BeginTransaction(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Commit commits a transaction, while optionally updating documents.
func (c *Client) Commit(ctx context.Context, req *firestorepb.CommitRequest, opts ...gax.CallOption) (*firestorepb.CommitResponse, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.Commit[0:len(c.CallOptions.Commit):len(c.CallOptions.Commit)], opts...)
var resp *firestorepb.CommitResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.Commit(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Rollback rolls back a transaction.
func (c *Client) Rollback(ctx context.Context, req *firestorepb.RollbackRequest, opts ...gax.CallOption) error {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.Rollback[0:len(c.CallOptions.Rollback):len(c.CallOptions.Rollback)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.Rollback(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// RunQuery runs a query.
func (c *Client) RunQuery(ctx context.Context, req *firestorepb.RunQueryRequest, opts ...gax.CallOption) (firestorepb.Firestore_RunQueryClient, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.RunQuery[0:len(c.CallOptions.RunQuery):len(c.CallOptions.RunQuery)], opts...)
var resp firestorepb.Firestore_RunQueryClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.RunQuery(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Write streams batches of document updates and deletes, in order.
func (c *Client) Write(ctx context.Context, opts ...gax.CallOption) (firestorepb.Firestore_WriteClient, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.Write[0:len(c.CallOptions.Write):len(c.CallOptions.Write)], opts...)
var resp firestorepb.Firestore_WriteClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.Write(ctx, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// Listen listens to changes.
func (c *Client) Listen(ctx context.Context, opts ...gax.CallOption) (firestorepb.Firestore_ListenClient, error) {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.Listen[0:len(c.CallOptions.Listen):len(c.CallOptions.Listen)], opts...)
var resp firestorepb.Firestore_ListenClient
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.Listen(ctx, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListCollectionIds lists all the collection IDs underneath a document.
func (c *Client) ListCollectionIds(ctx context.Context, req *firestorepb.ListCollectionIdsRequest, opts ...gax.CallOption) *StringIterator {
ctx = insertXGoog(ctx, c.xGoogHeader)
opts = append(c.CallOptions.ListCollectionIds[0:len(c.CallOptions.ListCollectionIds):len(c.CallOptions.ListCollectionIds)], opts...)
it := &StringIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]string, string, error) {
var resp *firestorepb.ListCollectionIdsResponse
req.PageToken = pageToken
if pageSize > math.MaxInt32 {
req.PageSize = math.MaxInt32
} else {
req.PageSize = int32(pageSize)
}
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ListCollectionIds(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.CollectionIds, resp.NextPageToken, nil
}
fetch := func(pageSize int, pageToken string) (string, error) {
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
if err != nil {
return "", err
}
it.items = append(it.items, items...)
return nextPageToken, nil
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
return it
}
// DocumentIterator manages a stream of *firestorepb.Document.
type DocumentIterator struct {
items []*firestorepb.Document
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []*firestorepb.Document, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *DocumentIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *DocumentIterator) Next() (*firestorepb.Document, error) {
var item *firestorepb.Document
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *DocumentIterator) bufLen() int {
return len(it.items)
}
func (it *DocumentIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// StringIterator manages a stream of string.
type StringIterator struct {
items []string
pageInfo *iterator.PageInfo
nextFunc func() error
// InternalFetch is for use by the Google Cloud Libraries only.
// It is not part of the stable interface of this package.
//
// InternalFetch returns results from a single call to the underlying RPC.
// The number of results is no greater than pageSize.
// If there are no more results, nextPageToken is empty and err is nil.
InternalFetch func(pageSize int, pageToken string) (results []string, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *StringIterator) PageInfo() *iterator.PageInfo {
return it.pageInfo
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *StringIterator) Next() (string, error) {
var item string
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *StringIterator) bufLen() int {
return len(it.items)
}
func (it *StringIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}

View File

@@ -0,0 +1,329 @@
// Copyright 2017, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// AUTO-GENERATED CODE. DO NOT EDIT.
package firestore_test
import (
"io"
firestore "cloud.google.com/go/firestore/apiv1beta1"
firestorepb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
)
func ExampleNewClient() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleClient_GetDocument() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.GetDocumentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetDocument(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListDocuments() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.ListDocumentsRequest{
// TODO: Fill request struct fields.
}
it := c.ListDocuments(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_CreateDocument() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.CreateDocumentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateDocument(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_UpdateDocument() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.UpdateDocumentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.UpdateDocument(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_DeleteDocument() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.DeleteDocumentRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteDocument(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_BatchGetDocuments() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.BatchGetDocumentsRequest{
// TODO: Fill request struct fields.
}
stream, err := c.BatchGetDocuments(ctx, req)
if err != nil {
// TODO: Handle error.
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_BeginTransaction() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.BeginTransactionRequest{
// TODO: Fill request struct fields.
}
resp, err := c.BeginTransaction(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_Commit() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.CommitRequest{
// TODO: Fill request struct fields.
}
resp, err := c.Commit(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_Rollback() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.RollbackRequest{
// TODO: Fill request struct fields.
}
err = c.Rollback(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_RunQuery() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.RunQueryRequest{
// TODO: Fill request struct fields.
}
stream, err := c.RunQuery(ctx, req)
if err != nil {
// TODO: Handle error.
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_Write() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
stream, err := c.Write(ctx)
if err != nil {
// TODO: Handle error.
}
go func() {
reqs := []*firestorepb.WriteRequest{
// TODO: Create requests.
}
for _, req := range reqs {
if err := stream.Send(req); err != nil {
// TODO: Handle error.
}
}
stream.CloseSend()
}()
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_Listen() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
stream, err := c.Listen(ctx)
if err != nil {
// TODO: Handle error.
}
go func() {
reqs := []*firestorepb.ListenRequest{
// TODO: Create requests.
}
for _, req := range reqs {
if err := stream.Send(req); err != nil {
// TODO: Handle error.
}
}
stream.CloseSend()
}()
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// TODO: handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_ListCollectionIds() {
ctx := context.Background()
c, err := firestore.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &firestorepb.ListCollectionIdsRequest{
// TODO: Fill request struct fields.
}
it := c.ListCollectionIds(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}

File diff suppressed because it is too large Load Diff

245
vendor/cloud.google.com/go/firestore/client.go generated vendored Normal file
View File

@@ -0,0 +1,245 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"io"
"strings"
"time"
"google.golang.org/api/iterator"
vkit "cloud.google.com/go/firestore/apiv1beta1"
"cloud.google.com/go/internal/version"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/grpc/metadata"
)
// resourcePrefixHeader is the name of the metadata header used to indicate
// the resource being operated on.
const resourcePrefixHeader = "google-cloud-resource-prefix"
// A Client provides access to the Firestore service.
type Client struct {
c *vkit.Client
projectID string
databaseID string // A client is tied to a single database.
}
// NewClient creates a new Firestore client that uses the given project.
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
vc, err := vkit.NewClient(ctx, opts...)
if err != nil {
return nil, err
}
vc.SetGoogleClientInfo("gccl", version.Repo)
c := &Client{
c: vc,
projectID: projectID,
databaseID: "(default)", // always "(default)", for now
}
return c, nil
}
// Close closes any resources held by the client.
//
// Close need not be called at program exit.
func (c *Client) Close() error {
return c.c.Close()
}
func (c *Client) path() string {
return fmt.Sprintf("projects/%s/databases/%s", c.projectID, c.databaseID)
}
func withResourceHeader(ctx context.Context, resource string) context.Context {
md, _ := metadata.FromOutgoingContext(ctx)
md = md.Copy()
md[resourcePrefixHeader] = []string{resource}
return metadata.NewOutgoingContext(ctx, md)
}
// Collection creates a reference to a collection with the given path.
// A path is a sequence of IDs separated by slashes.
//
// Collection returns nil if path contains an even number of IDs or any ID is empty.
func (c *Client) Collection(path string) *CollectionRef {
coll, _ := c.idsToRef(strings.Split(path, "/"), c.path())
return coll
}
// Doc creates a reference to a document with the given path.
// A path is a sequence of IDs separated by slashes.
//
// Doc returns nil if path contains an odd number of IDs or any ID is empty.
func (c *Client) Doc(path string) *DocumentRef {
_, doc := c.idsToRef(strings.Split(path, "/"), c.path())
return doc
}
func (c *Client) idsToRef(IDs []string, dbPath string) (*CollectionRef, *DocumentRef) {
if len(IDs) == 0 {
return nil, nil
}
for _, id := range IDs {
if id == "" {
return nil, nil
}
}
coll := newTopLevelCollRef(c, dbPath, IDs[0])
i := 1
for i < len(IDs) {
doc := newDocRef(coll, IDs[i])
i++
if i == len(IDs) {
return nil, doc
}
coll = newCollRefWithParent(c, doc, IDs[i])
i++
}
return coll, nil
}
// GetAll retrieves multiple documents with a single call. The DocumentSnapshots are
// returned in the order of the given DocumentRefs.
//
// If a document is not present, the corresponding DocumentSnapshot will be nil.
func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*DocumentSnapshot, error) {
if err := checkTransaction(ctx); err != nil {
return nil, err
}
var docNames []string
for _, dr := range docRefs {
if dr == nil {
return nil, errNilDocRef
}
docNames = append(docNames, dr.Path)
}
req := &pb.BatchGetDocumentsRequest{
Database: c.path(),
Documents: docNames,
}
streamClient, err := c.c.BatchGetDocuments(withResourceHeader(ctx, req.Database), req)
if err != nil {
return nil, err
}
// Read results from the stream and add them to a map.
docMap := map[string]*pb.Document{}
for {
res, err := streamClient.Recv()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
switch x := res.Result.(type) {
case *pb.BatchGetDocumentsResponse_Found:
docMap[x.Found.Name] = x.Found
case *pb.BatchGetDocumentsResponse_Missing:
if docMap[x.Missing] != nil {
return nil, fmt.Errorf("firestore: %q both missing and present", x.Missing)
}
docMap[x.Missing] = nil
default:
return nil, errors.New("firestore: unknown BatchGetDocumentsResponse result type")
}
}
// Put the documents we've gathered in the same order as the requesting slice of
// DocumentRefs.
docs := make([]*DocumentSnapshot, len(docNames))
for i, name := range docNames {
pbDoc, ok := docMap[name]
if !ok {
return nil, fmt.Errorf("firestore: passed %q to BatchGetDocuments but never saw response", name)
}
if pbDoc != nil {
doc, err := newDocumentSnapshot(docRefs[i], pbDoc, c)
if err != nil {
return nil, err
}
docs[i] = doc
}
}
return docs, nil
}
// Collections returns an interator over the top-level collections.
func (c *Client) Collections(ctx context.Context) *CollectionIterator {
it := &CollectionIterator{
err: checkTransaction(ctx),
client: c,
it: c.c.ListCollectionIds(
withResourceHeader(ctx, c.path()),
&pb.ListCollectionIdsRequest{Parent: c.path()}),
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
it.fetch,
func() int { return len(it.items) },
func() interface{} { b := it.items; it.items = nil; return b })
return it
}
// Batch returns a WriteBatch.
func (c *Client) Batch() *WriteBatch {
return &WriteBatch{c: c}
}
// commit calls the Commit RPC outside of a transaction.
func (c *Client) commit(ctx context.Context, ws []*pb.Write) (*WriteResult, error) {
if err := checkTransaction(ctx); err != nil {
return nil, err
}
req := &pb.CommitRequest{
Database: c.path(),
Writes: ws,
}
res, err := c.c.Commit(withResourceHeader(ctx, req.Database), req)
if err != nil {
return nil, err
}
if len(res.WriteResults) == 0 {
return nil, errors.New("firestore: missing WriteResult")
}
return writeResultFromProto(res.WriteResults[0])
}
// A WriteResult is returned by methods that write documents.
type WriteResult struct {
// The time at which the document was updated, or created if it did not
// previously exist. Writes that do not actually change the document do
// not change the update time.
UpdateTime time.Time
}
func writeResultFromProto(wr *pb.WriteResult) (*WriteResult, error) {
t, err := ptypes.Timestamp(wr.UpdateTime)
if err != nil {
t = time.Time{}
// TODO(jba): Follow up if Delete is supposed to return a nil timestamp.
}
return &WriteResult{UpdateTime: t}, nil
}

221
vendor/cloud.google.com/go/firestore/client_test.go generated vendored Normal file
View File

@@ -0,0 +1,221 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"cloud.google.com/go/internal/pretty"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
)
var testClient = &Client{
projectID: "projectID",
databaseID: "(default)",
}
func TestClientCollectionAndDoc(t *testing.T) {
coll1 := testClient.Collection("X")
db := "projects/projectID/databases/(default)"
wantc1 := &CollectionRef{
c: testClient,
parentPath: db,
Parent: nil,
ID: "X",
Path: "projects/projectID/databases/(default)/documents/X",
Query: Query{c: testClient, collectionID: "X", parentPath: db},
}
if !testEqual(coll1, wantc1) {
t.Fatalf("got\n%+v\nwant\n%+v", coll1, wantc1)
}
doc1 := testClient.Doc("X/a")
wantd1 := &DocumentRef{
Parent: coll1,
ID: "a",
Path: "projects/projectID/databases/(default)/documents/X/a",
}
if !testEqual(doc1, wantd1) {
t.Fatalf("got %+v, want %+v", doc1, wantd1)
}
coll2 := testClient.Collection("X/a/Y")
parentPath := "projects/projectID/databases/(default)/documents/X/a"
wantc2 := &CollectionRef{
c: testClient,
parentPath: parentPath,
Parent: doc1,
ID: "Y",
Path: "projects/projectID/databases/(default)/documents/X/a/Y",
Query: Query{c: testClient, collectionID: "Y", parentPath: parentPath},
}
if !testEqual(coll2, wantc2) {
t.Fatalf("\ngot %+v\nwant %+v", coll2, wantc2)
}
doc2 := testClient.Doc("X/a/Y/b")
wantd2 := &DocumentRef{
Parent: coll2,
ID: "b",
Path: "projects/projectID/databases/(default)/documents/X/a/Y/b",
}
if !testEqual(doc2, wantd2) {
t.Fatalf("got %+v, want %+v", doc2, wantd2)
}
}
func TestClientCollDocErrors(t *testing.T) {
for _, badColl := range []string{"", "/", "/a/", "/a/b", "a/b/", "a//b"} {
coll := testClient.Collection(badColl)
if coll != nil {
t.Errorf("coll path %q: got %+v, want nil", badColl, coll)
}
}
for _, badDoc := range []string{"", "a", "/", "/a", "a/", "a/b/c", "a//b/c"} {
doc := testClient.Doc(badDoc)
if doc != nil {
t.Errorf("doc path %q: got %+v, want nil", badDoc, doc)
}
}
}
func TestGetAll(t *testing.T) {
ctx := context.Background()
const dbPath = "projects/projectID/databases/(default)"
c, srv := newMock(t)
defer c.Close()
wantPBDocs := []*pb.Document{
{
Name: dbPath + "/documents/C/a",
CreateTime: aTimestamp,
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(2)},
},
nil,
{
Name: dbPath + "/documents/C/c",
CreateTime: aTimestamp,
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(1)},
},
}
srv.addRPC(
&pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{
dbPath + "/documents/C/a",
dbPath + "/documents/C/b",
dbPath + "/documents/C/c",
},
},
[]interface{}{
// deliberately put these out of order
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[2]},
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[0]},
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Missing{dbPath + "/documents/C/b"},
},
},
)
coll := c.Collection("C")
var docRefs []*DocumentRef
for _, name := range []string{"a", "b", "c"} {
docRefs = append(docRefs, coll.Doc(name))
}
docs, err := c.GetAll(ctx, docRefs)
if err != nil {
t.Fatal(err)
}
if got, want := len(docs), len(wantPBDocs); got != want {
t.Errorf("got %d docs, wanted %d", got, want)
}
for i, got := range docs {
var want *DocumentSnapshot
if wantPBDocs[i] != nil {
want, err = newDocumentSnapshot(docRefs[i], wantPBDocs[i], c)
if err != nil {
t.Fatal(err)
}
}
if !testEqual(got, want) {
got.c = nil
want.c = nil
t.Errorf("#%d: got %+v, want %+v", i, pretty.Value(got), pretty.Value(want))
}
}
}
func TestGetAllErrors(t *testing.T) {
ctx := context.Background()
const (
dbPath = "projects/projectID/databases/(default)"
docPath = dbPath + "/documents/C/a"
)
c, srv := newMock(t)
if _, err := c.GetAll(ctx, []*DocumentRef{nil}); err != errNilDocRef {
t.Errorf("got %v, want errNilDocRef", err)
}
// Internal server error.
srv.addRPC(
&pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{docPath},
},
[]interface{}{grpc.Errorf(codes.Internal, "")},
)
_, err := c.GetAll(ctx, []*DocumentRef{c.Doc("C/a")})
codeEq(t, "GetAll #1", codes.Internal, err)
// Doc appears as both found and missing (server bug).
srv.reset()
srv.addRPC(
&pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{docPath},
},
[]interface{}{
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{&pb.Document{Name: docPath}},
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Missing{docPath},
},
},
)
if _, err := c.GetAll(ctx, []*DocumentRef{c.Doc("C/a")}); err == nil {
t.Error("got nil, want error")
}
// Doc never appears (server bug).
srv.reset()
srv.addRPC(
&pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{docPath},
},
[]interface{}{},
)
if _, err := c.GetAll(ctx, []*DocumentRef{c.Doc("C/a")}); err == nil {
t.Error("got nil, want error")
}
}

124
vendor/cloud.google.com/go/firestore/collref.go generated vendored Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"math/rand"
"os"
"reflect"
"sync"
"time"
"golang.org/x/net/context"
)
// A CollectionRef is a reference to Firestore collection.
type CollectionRef struct {
c *Client
// Typically Parent.Path, or c.path if Parent is nil.
// May be different if this CollectionRef was created from a stored reference
// to a different project/DB.
parentPath string
// Parent is the document of which this collection is a part. It is
// nil for top-level collections.
Parent *DocumentRef
// The full resource path of the collection: "projects/P/databases/D/documents..."
Path string
// ID is the collection identifier.
ID string
// Use the methods of Query on a CollectionRef to create and run queries.
Query
}
func (c1 *CollectionRef) equal(c2 *CollectionRef) bool {
return c1.c == c2.c &&
c1.parentPath == c2.parentPath &&
c1.Parent.equal(c2.Parent) &&
c1.Path == c2.Path &&
c1.ID == c2.ID &&
reflect.DeepEqual(c1.Query, c2.Query)
}
func newTopLevelCollRef(c *Client, dbPath, id string) *CollectionRef {
return &CollectionRef{
c: c,
ID: id,
parentPath: dbPath,
Path: dbPath + "/documents/" + id,
Query: Query{c: c, collectionID: id, parentPath: dbPath},
}
}
func newCollRefWithParent(c *Client, parent *DocumentRef, id string) *CollectionRef {
return &CollectionRef{
c: c,
Parent: parent,
ID: id,
parentPath: parent.Path,
Path: parent.Path + "/" + id,
Query: Query{c: c, collectionID: id, parentPath: parent.Path},
}
}
// Doc returns a DocumentRef that refers to the document in the collection with the
// given identifier.
func (c *CollectionRef) Doc(id string) *DocumentRef {
if c == nil {
return nil
}
return newDocRef(c, id)
}
// NewDoc returns a DocumentRef with a uniquely generated ID.
func (c *CollectionRef) NewDoc() *DocumentRef {
return c.Doc(uniqueID())
}
// Add generates a DocumentRef with a unique ID. It then creates the document
// with the given data, which can be a map[string]interface{}, a struct or a
// pointer to a struct.
//
// Add returns an error in the unlikely event that a document with the same ID
// already exists.
func (c *CollectionRef) Add(ctx context.Context, data interface{}) (*DocumentRef, *WriteResult, error) {
d := c.NewDoc()
wr, err := d.Create(ctx, data)
if err != nil {
return nil, nil, err
}
return d, wr, nil
}
const alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var (
rngMu sync.Mutex
rng = rand.New(rand.NewSource(time.Now().UnixNano() ^ int64(os.Getpid())))
)
func uniqueID() string {
var b [20]byte
rngMu.Lock()
for i := 0; i < len(b); i++ {
b[i] = alphanum[rng.Intn(len(alphanum))]
}
rngMu.Unlock()
return string(b[:])
}

97
vendor/cloud.google.com/go/firestore/collref_test.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"testing"
"github.com/golang/protobuf/proto"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
)
func TestDoc(t *testing.T) {
coll := testClient.Collection("C")
got := coll.Doc("d")
want := &DocumentRef{
Parent: coll,
ID: "d",
Path: "projects/projectID/databases/(default)/documents/C/d",
}
if !testEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
}
func TestNewDoc(t *testing.T) {
c := &Client{}
coll := c.Collection("C")
got := coll.NewDoc()
if got.Parent != coll {
t.Errorf("got %v, want %v", got.Parent, coll)
}
if len(got.ID) != 20 {
t.Errorf("got %d-char ID, wanted 20", len(got.ID))
}
got2 := coll.NewDoc()
if got.ID == got2.ID {
t.Error("got same ID")
}
}
func TestAdd(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
wantReq := commitRequestForSet()
w := wantReq.Writes[0]
w.CurrentDocument = &pb.Precondition{
ConditionType: &pb.Precondition_Exists{false},
}
srv.addRPCAdjust(wantReq, commitResponseForSet, func(gotReq proto.Message) {
// We can't know the doc ID before Add is called, so we take it from
// the request.
w.Operation.(*pb.Write_Update).Update.Name = gotReq.(*pb.CommitRequest).Writes[0].Operation.(*pb.Write_Update).Update.Name
})
_, wr, err := c.Collection("C").Add(ctx, testData)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %v, want %v", wr, writeResultForSet)
}
}
func TestNilErrors(t *testing.T) {
ctx := context.Background()
c, _ := newMock(t)
// Test that a nil CollectionRef results in a nil DocumentRef and errors
// where possible.
coll := c.Collection("a/b") // nil because "a/b" denotes a doc.
if coll != nil {
t.Fatal("collection not nil")
}
if got := coll.Doc("d"); got != nil {
t.Fatalf("got %v, want nil", got)
}
if got := coll.NewDoc(); got != nil {
t.Fatalf("got %v, want nil", got)
}
if _, _, err := coll.Add(ctx, testData); err != errNilDocRef {
t.Fatalf("got <%v>, want <%v>", err, errNilDocRef)
}
}

220
vendor/cloud.google.com/go/firestore/doc.go generated vendored Normal file
View File

@@ -0,0 +1,220 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// DO NOT EDIT doc.go. Modify internal/doc.template, then run make -C internal.
/*
Package firestore provides a client for reading and writing to a Cloud Firestore
database.
See https://cloud.google.com/firestore/docs for an introduction
to Cloud Firestore and additional help on using the Firestore API.
Creating a Client
To start working with this package, create a client with a project ID:
ctx := context.Background()
client, err := firestore.NewClient(ctx, "projectID")
if err != nil {
// TODO: Handle error.
}
CollectionRefs and DocumentRefs
In Firestore, documents are sets of key-value pairs, and collections are groups of
documents. A Firestore database consists of a hierarchy of alternating collections
and documents, referred to by slash-separated paths like
"States/California/Cities/SanFrancisco".
This client is built around references to collections and documents. CollectionRefs
and DocumentRefs are lightweight values that refer to the corresponding database
entities. Creating a ref does not involve any network traffic.
states := client.Collection("States")
ny := states.Doc("NewYork")
// Or, in a single call:
ny = client.Doc("States/NewYork")
Reading
Use DocumentRef.Get to read a document. The result is a DocumentSnapshot.
Call its Data method to obtain the entire document contents as a map.
docsnap, err := ny.Get(ctx)
if err != nil {
// TODO: Handle error.
}
dataMap := docsnap.Data()
fmt.Println(dataMap)
You can also obtain a single field with DataAt, or extract the data into a struct
with DataTo. With the type definition
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
we can extract the document's data into a value of type State:
var nyData State
if err := docsnap.DataTo(&nyData); err != nil {
// TODO: Handle error.
}
Note that this client supports struct tags beginning with "firestore:" that work like
the tags of the encoding/json package, letting you rename fields, ignore them, or
omit their values when empty.
To retrieve multiple documents from their references in a single call, use
Client.GetAll.
docsnaps, err := client.GetAll(ctx, []*firestore.DocumentRef{
states.Doc("Wisconsin"), states.Doc("Ohio"),
})
if err != nil {
// TODO: Handle error.
}
for _, ds := range docsnaps {
_ = ds // TODO: Use ds.
}
Writing
For writing individual documents, use the methods on DocumentReference.
Create creates a new document.
wr, err := ny.Create(ctx, State{
Capital: "Albany",
Population: 19.8,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr)
The first return value is a WriteResult, which contains the time
at which the document was updated.
Create fails if the document exists. Another method, Set, either replaces an existing
document or creates a new one.
ca := states.Doc("California")
_, err = ca.Set(ctx, State{
Capital: "Sacramento",
Population: 39.14,
})
To update some fields of an existing document, use UpdateMap, UpdateStruct or
UpdatePaths. For UpdateMap, the keys of the map specify which fields to change. The
others are untouched.
_, err = ca.UpdateMap(ctx, map[string]interface{}{"pop": 39.2})
For UpdateStruct, you must explicitly provide the fields to update. The field names
must match exactly.
_, err = ca.UpdateStruct(ctx, []string{"pop"}, State{Population: 39.2})
Use DocumentRef.Delete to delete a document.
_, err = ny.Delete(ctx)
Preconditions
You can condition Deletes or Updates on when a document was last changed. Specify
these preconditions as an option to a Delete or Update method. The check and the
write happen atomically with a single RPC.
docsnap, err = ca.Get(ctx)
if err != nil {
// TODO: Handle error.
}
_, err = ca.UpdateStruct(ctx, []string{"capital"}, State{Capital: "Sacramento"},
firestore.LastUpdateTime(docsnap.UpdateTime))
Here we update a doc only if it hasn't changed since we read it.
You could also do this with a transaction.
To perform multiple writes at once, use a WriteBatch. Its methods chain
for convenience.
WriteBatch.Commit sends the collected writes to the server, where they happen
atomically.
writeResults, err := client.Batch().
Create(ny, State{Capital: "Albany"}).
UpdateStruct(ca, []string{"capital"}, State{Capital: "Sacramento"}).
Delete(client.Doc("States/WestDakota")).
Commit(ctx)
Queries
You can use SQL to select documents from a collection. Begin with the collection, and
build up a query using Select, Where and other methods of Query.
q := states.Where("pop", ">", 10).OrderBy("pop", firestore.Desc)
Call the Query's Documents method to get an iterator, and use it like
the other Google Cloud Client iterators.
iter := q.Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(doc.Data())
}
To get all the documents in a collection, you can use the collection itself
as a query.
iter = client.Collection("States").Documents(ctx)
Transactions
Use a transaction to execute reads and writes atomically. All reads must happen
before any writes. Transaction creation, commit, rollback and retry are handled for
you by the Client.RunTransaction method; just provide a function and use the
read and write methods of the Transaction passed to it.
ny := client.Doc("States/NewYork")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(ny) // tx.Get, NOT ny.Get!
if err != nil {
return err
}
pop, err := doc.DataAt("pop")
if err != nil {
return err
}
return tx.UpdateStruct(ny, []string{"pop"},
State{Population: pop.(float64) + 0.2})
})
if err != nil {
// TODO: Handle error.
}
Authentication
See examples of authorization and authentication at
https://godoc.org/cloud.google.com/go#pkg-examples.
*/
package firestore

599
vendor/cloud.google.com/go/firestore/docref.go generated vendored Normal file
View File

@@ -0,0 +1,599 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"reflect"
"sort"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
vkit "cloud.google.com/go/firestore/apiv1beta1"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
)
var errNilDocRef = errors.New("firestore: nil DocumentRef")
// A DocumentRef is a reference to a Firestore document.
type DocumentRef struct {
// The CollectionRef that this document is a part of. Never nil.
Parent *CollectionRef
// The full resource path of the document: "projects/P/databases/D/documents..."
Path string
// The ID of the document: the last component of the resource path.
ID string
}
func newDocRef(parent *CollectionRef, id string) *DocumentRef {
return &DocumentRef{
Parent: parent,
ID: id,
Path: parent.Path + "/" + id,
}
}
func (d1 *DocumentRef) equal(d2 *DocumentRef) bool {
if d1 == nil || d2 == nil {
return d1 == d2
}
return d1.Parent.equal(d2.Parent) && d1.Path == d2.Path && d1.ID == d2.ID
}
// Collection returns a reference to sub-collection of this document.
func (d *DocumentRef) Collection(id string) *CollectionRef {
return newCollRefWithParent(d.Parent.c, d, id)
}
// Get retrieves the document. It returns an error if the document does not exist.
func (d *DocumentRef) Get(ctx context.Context) (*DocumentSnapshot, error) {
if err := checkTransaction(ctx); err != nil {
return nil, err
}
if d == nil {
return nil, errNilDocRef
}
doc, err := d.Parent.c.c.GetDocument(withResourceHeader(ctx, d.Parent.c.path()),
&pb.GetDocumentRequest{Name: d.Path})
// TODO(jba): verify that GetDocument returns NOT_FOUND.
if err != nil {
return nil, err
}
return newDocumentSnapshot(d, doc, d.Parent.c)
}
// Create creates the document with the given data.
// It returns an error if a document with the same ID already exists.
//
// The data argument can be a map with string keys, a struct, or a pointer to a
// struct. The map keys or exported struct fields become the fields of the firestore
// document.
// The values of data are converted to Firestore values as follows:
//
// - bool converts to Bool.
// - string converts to String.
// - int, int8, int16, int32 and int64 convert to Integer.
// - uint8, uint16 and uint32 convert to Integer. uint64 is disallowed,
// because it can represent values that cannot be represented in an int64, which
// is the underlying type of a Integer.
// - float32 and float64 convert to Double.
// - []byte converts to Bytes.
// - time.Time converts to Timestamp.
// - latlng.LatLng converts to GeoPoint. latlng is the package
// "google.golang.org/genproto/googleapis/type/latlng".
// - Slices convert to Array.
// - Maps and structs convert to Map.
// - nils of any type convert to Null.
//
// Pointers and interface{} are also permitted, and their elements processed
// recursively.
//
// Struct fields can have tags like those used by the encoding/json package. Tags
// begin with "firestore:" and are followed by "-", meaning "ignore this field," or
// an alternative name for the field. Following the name, these comma-separated
// options may be provided:
//
// - omitempty: Do not encode this field if it is empty. A value is empty
// if it is a zero value, or an array, slice or map of length zero.
// - serverTimestamp: The field must be of type time.Time. When writing, if
// the field has the zero value, the server will populate the stored document with
// the time that the request is processed.
func (d *DocumentRef) Create(ctx context.Context, data interface{}) (*WriteResult, error) {
ws, err := d.newReplaceWrites(data, nil, Exists(false))
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
// Set creates or overwrites the document with the given data. See DocumentRef.Create
// for the acceptable values of data. Without options, Set overwrites the document
// completely. Specify one of the Merge options to preserve an existing document's
// fields.
func (d *DocumentRef) Set(ctx context.Context, data interface{}, opts ...SetOption) (*WriteResult, error) {
ws, err := d.newReplaceWrites(data, opts, nil)
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
// Delete deletes the document. If the document doesn't exist, it does nothing
// and returns no error.
func (d *DocumentRef) Delete(ctx context.Context, preconds ...Precondition) (*WriteResult, error) {
ws, err := d.newDeleteWrites(preconds)
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
func (d *DocumentRef) newReplaceWrites(data interface{}, opts []SetOption, p Precondition) ([]*pb.Write, error) {
if d == nil {
return nil, errNilDocRef
}
origFieldPaths, allPaths, err := processSetOptions(opts)
isMerge := len(origFieldPaths) > 0 || allPaths // was some Merge option specified?
if err != nil {
return nil, err
}
doc, serverTimestampPaths, err := toProtoDocument(data)
if err != nil {
return nil, err
}
if len(origFieldPaths) > 0 {
// Keep only data fields corresponding to the given field paths.
doc.Fields = applyFieldPaths(doc.Fields, origFieldPaths, nil)
}
doc.Name = d.Path
var fieldPaths []FieldPath
if allPaths {
// MergeAll was passed. Check that the data is a map, and extract its field paths.
v := reflect.ValueOf(data)
if v.Kind() != reflect.Map {
return nil, errors.New("firestore: MergeAll can only be specified with map data")
}
fieldPaths = fieldPathsFromMap(v, nil)
} else if len(origFieldPaths) > 0 {
// Remove server timestamp paths that are not in the list of paths to merge.
// Note: this is technically O(n^2), but it is unlikely that there is more
// than one server timestamp path.
serverTimestampPaths = removePathsIf(serverTimestampPaths, func(fp FieldPath) bool {
return !fp.in(origFieldPaths)
})
// Remove server timestamp fields from fieldPaths. Those fields were removed
// from the document by toProtoDocument, so they should not be in the update
// mask.
// Note: this is technically O(n^2), but it is unlikely that there is
// more than one server timestamp path.
fieldPaths = removePathsIf(origFieldPaths, func(fp FieldPath) bool {
return fp.in(serverTimestampPaths)
})
// Check that all the remaining field paths in the merge option are in the document.
for _, fp := range fieldPaths {
if _, err := valueAtPath(fp, doc.Fields); err != nil {
return nil, err
}
}
}
var pc *pb.Precondition
if p != nil {
pc, err = p.preconditionProto()
if err != nil {
return nil, err
}
}
var w *pb.Write
switch {
case len(fieldPaths) > 0:
// There are field paths, so we need an update mask.
sfps := toServiceFieldPaths(fieldPaths)
sort.Strings(sfps) // TODO(jba): make tests pass without this
w = &pb.Write{
Operation: &pb.Write_Update{doc},
UpdateMask: &pb.DocumentMask{FieldPaths: sfps},
CurrentDocument: pc,
}
case isMerge && pc != nil:
// There were field paths, but they all got removed.
// The write does nothing but enforce the precondition.
w = &pb.Write{CurrentDocument: pc}
case !isMerge:
// Set without merge, so no update mask.
w = &pb.Write{
Operation: &pb.Write_Update{doc},
CurrentDocument: pc,
}
}
return d.writeWithTransform(w, serverTimestampPaths), nil
}
// Create a new map that contains only the field paths in fps.
func applyFieldPaths(fields map[string]*pb.Value, fps []FieldPath, root FieldPath) map[string]*pb.Value {
r := map[string]*pb.Value{}
for k, v := range fields {
kpath := root.with(k)
if kpath.in(fps) {
r[k] = v
} else if mv := v.GetMapValue(); mv != nil {
if m2 := applyFieldPaths(mv.Fields, fps, kpath); m2 != nil {
r[k] = &pb.Value{&pb.Value_MapValue{&pb.MapValue{m2}}}
}
}
}
if len(r) == 0 {
return nil
}
return r
}
func fieldPathsFromMap(vmap reflect.Value, prefix FieldPath) []FieldPath {
// vmap is a map and its keys are strings.
// Each map key denotes a field; no splitting or escaping.
var fps []FieldPath
for _, k := range vmap.MapKeys() {
v := vmap.MapIndex(k)
fp := prefix.with(k.String())
if vm := extractMap(v); vm.IsValid() {
fps = append(fps, fieldPathsFromMap(vm, fp)...)
} else if v.Interface() != ServerTimestamp {
// ServerTimestamp fields do not go into the update mask.
fps = append(fps, fp)
}
}
return fps
}
func extractMap(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Map:
return v
case reflect.Interface:
return extractMap(v.Elem())
default:
return reflect.Value{}
}
}
// removePathsIf creates a new slice of FieldPaths that contains
// exactly those elements of fps for which pred returns false.
func removePathsIf(fps []FieldPath, pred func(FieldPath) bool) []FieldPath {
var result []FieldPath
for _, fp := range fps {
if !pred(fp) {
result = append(result, fp)
}
}
return result
}
func (d *DocumentRef) newDeleteWrites(preconds []Precondition) ([]*pb.Write, error) {
if d == nil {
return nil, errNilDocRef
}
pc, err := processPreconditionsForDelete(preconds)
if err != nil {
return nil, err
}
return []*pb.Write{{
Operation: &pb.Write_Delete{d.Path},
CurrentDocument: pc,
}}, nil
}
func (d *DocumentRef) newUpdateMapWrites(data map[string]interface{}, preconds []Precondition) ([]*pb.Write, error) {
// Collect all the (top-level) keys map; they will comprise the update mask.
// Also, translate the map into a sequence of FieldPathUpdates.
var fps []FieldPath
var fpus []FieldPathUpdate
for k, v := range data {
fp, err := parseDotSeparatedString(k)
if err != nil {
return nil, err
}
fps = append(fps, fp)
fpus = append(fpus, FieldPathUpdate{Path: fp, Value: v})
}
// Check that there are no duplicate field paths, and that no field
// path is a prefix of another.
if err := checkNoDupOrPrefix(fps); err != nil {
return nil, err
}
// Re-create the map from the field paths and their corresponding values. A field path
// with a Delete value will not appear in the map but it will appear in the
// update mask, which will cause it to be deleted.
m := createMapFromFieldPathUpdates(fpus)
return d.newUpdateWrites(m, fps, preconds)
}
func (d *DocumentRef) newUpdateStructWrites(fieldPaths []string, data interface{}, preconds []Precondition) ([]*pb.Write, error) {
if !isStructOrStructPtr(data) {
return nil, errors.New("firestore: data is not struct or struct pointer")
}
fps, err := parseDotSeparatedStrings(fieldPaths)
if err != nil {
return nil, err
}
if err := checkNoDupOrPrefix(fps); err != nil {
return nil, err
}
return d.newUpdateWrites(data, fps, preconds)
}
func (d *DocumentRef) newUpdatePathWrites(data []FieldPathUpdate, preconds []Precondition) ([]*pb.Write, error) {
var fps []FieldPath
for _, fpu := range data {
if err := fpu.Path.validate(); err != nil {
return nil, err
}
fps = append(fps, fpu.Path)
}
if err := checkNoDupOrPrefix(fps); err != nil {
return nil, err
}
m := createMapFromFieldPathUpdates(data)
return d.newUpdateWrites(m, fps, preconds)
}
// newUpdateWrites creates Write operations for an update.
func (d *DocumentRef) newUpdateWrites(data interface{}, fieldPaths []FieldPath, preconds []Precondition) ([]*pb.Write, error) {
if len(fieldPaths) == 0 {
return nil, errors.New("firestore: no paths to update")
}
if d == nil {
return nil, errNilDocRef
}
pc, err := processPreconditionsForUpdate(preconds)
if err != nil {
return nil, err
}
doc, serverTimestampPaths, err := toProtoDocument(data)
if err != nil {
return nil, err
}
sfps := toServiceFieldPaths(fieldPaths)
doc.Name = d.Path
return d.writeWithTransform(&pb.Write{
Operation: &pb.Write_Update{doc},
UpdateMask: &pb.DocumentMask{FieldPaths: sfps},
CurrentDocument: pc,
}, serverTimestampPaths), nil
}
var requestTimeTransform = &pb.DocumentTransform_FieldTransform_SetToServerValue{
pb.DocumentTransform_FieldTransform_REQUEST_TIME,
}
func (d *DocumentRef) writeWithTransform(w *pb.Write, serverTimestampFieldPaths []FieldPath) []*pb.Write {
var ws []*pb.Write
if w != nil {
ws = append(ws, w)
}
if len(serverTimestampFieldPaths) > 0 {
ws = append(ws, d.newTransform(serverTimestampFieldPaths))
}
return ws
}
func (d *DocumentRef) newTransform(serverTimestampFieldPaths []FieldPath) *pb.Write {
sort.Sort(byPath(serverTimestampFieldPaths)) // TODO(jba): make tests pass without this
var fts []*pb.DocumentTransform_FieldTransform
for _, p := range serverTimestampFieldPaths {
fts = append(fts, &pb.DocumentTransform_FieldTransform{
FieldPath: p.toServiceFieldPath(),
TransformType: requestTimeTransform,
})
}
return &pb.Write{
Operation: &pb.Write_Transform{
&pb.DocumentTransform{
Document: d.Path,
FieldTransforms: fts,
// TODO(jba): should the transform have the same preconditions as the write?
},
},
}
}
var (
// Delete is used as a value in a call to UpdateMap to indicate that the
// corresponding key should be deleted.
Delete = new(int)
// Not new(struct{}), because addresses of zero-sized values
// may not be unique.
// ServerTimestamp is used as a value in a call to UpdateMap to indicate that the
// key's value should be set to the time at which the server processed
// the request.
ServerTimestamp = new(int)
)
// UpdateMap updates the document using the given data. Map keys replace the stored
// values, but other fields of the stored document are untouched.
// See DocumentRef.Create for acceptable map values.
//
// If a map key is a multi-element field path, like "a.b", then only key "b" of
// the map value at "a" is changed; the rest of the map is preserved.
// For example, if the stored data is
// {"a": {"b": 1, "c": 2}}
// then
// UpdateMap({"a": {"b": 3}}) => {"a": {"b": 3}}
// while
// UpdateMap({"a.b": 3}) => {"a": {"b": 3, "c": 2}}
//
// To delete a key, specify it in the input with a value of firestore.Delete.
//
// Field paths expressed as map keys must not contain any of the runes "˜*/[]".
// Use UpdatePaths instead for such paths.
//
// UpdateMap returns an error if the document does not exist.
func (d *DocumentRef) UpdateMap(ctx context.Context, data map[string]interface{}, preconds ...Precondition) (*WriteResult, error) {
ws, err := d.newUpdateMapWrites(data, preconds)
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
func isStructOrStructPtr(x interface{}) bool {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Struct {
return true
}
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
return true
}
return false
}
// UpdateStruct updates the given field paths of the stored document from the fields
// of data, which must be a struct or a pointer to a struct. Other fields of the
// stored document are untouched.
// See DocumentRef.Create for the acceptable values of the struct's fields.
//
// Each element of fieldPaths is a single field or a dot-separated sequence of
// fields, none of which contain the runes "˜*/[]".
//
// If an element of fieldPaths does not have a corresponding field in the struct,
// that key is deleted from the stored document.
//
// UpdateStruct returns an error if the document does not exist.
func (d *DocumentRef) UpdateStruct(ctx context.Context, fieldPaths []string, data interface{}, preconds ...Precondition) (*WriteResult, error) {
ws, err := d.newUpdateStructWrites(fieldPaths, data, preconds)
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
// A FieldPathUpdate describes an update to a value referred to by a FieldPath.
// See DocumentRef.Create for acceptable values.
// To delete a field, specify firestore.Delete as the value.
type FieldPathUpdate struct {
Path FieldPath
Value interface{}
}
// UpdatePaths updates the document using the given data. The values at the given
// field paths are replaced, but other fields of the stored document are untouched.
func (d *DocumentRef) UpdatePaths(ctx context.Context, data []FieldPathUpdate, preconds ...Precondition) (*WriteResult, error) {
ws, err := d.newUpdatePathWrites(data, preconds)
if err != nil {
return nil, err
}
return d.Parent.c.commit(ctx, ws)
}
// Collections returns an interator over the immediate sub-collections of the document.
func (d *DocumentRef) Collections(ctx context.Context) *CollectionIterator {
client := d.Parent.c
it := &CollectionIterator{
err: checkTransaction(ctx),
client: client,
parent: d,
it: client.c.ListCollectionIds(
withResourceHeader(ctx, client.path()),
&pb.ListCollectionIdsRequest{Parent: d.Path}),
}
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
it.fetch,
func() int { return len(it.items) },
func() interface{} { b := it.items; it.items = nil; return b })
return it
}
// CollectionIterator is an iterator over sub-collections of a document.
type CollectionIterator struct {
client *Client
parent *DocumentRef
it *vkit.StringIterator
pageInfo *iterator.PageInfo
nextFunc func() error
items []*CollectionRef
err error
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *CollectionIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
// Next returns the next result. Its second return value is iterator.Done if there
// are no more results. Once Next returns Done, all subsequent calls will return
// Done.
func (it *CollectionIterator) Next() (*CollectionRef, error) {
if err := it.nextFunc(); err != nil {
return nil, err
}
item := it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *CollectionIterator) fetch(pageSize int, pageToken string) (string, error) {
if it.err != nil {
return "", it.err
}
return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
id, err := it.it.Next()
if err != nil {
return err
}
var cr *CollectionRef
if it.parent == nil {
cr = newTopLevelCollRef(it.client, it.client.path(), id)
} else {
cr = newCollRefWithParent(it.client, it.parent, id)
}
it.items = append(it.items, cr)
return nil
})
}
// GetAll returns all the collections remaining from the iterator.
func (it *CollectionIterator) GetAll() ([]*CollectionRef, error) {
var crs []*CollectionRef
for {
cr, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}
crs = append(crs, cr)
}
return crs, nil
}
// Common fetch code for iterators that are backed by vkit iterators.
// TODO(jba): dedup with same function in logging/logadmin.
func iterFetch(pageSize int, pageToken string, pi *iterator.PageInfo, next func() error) (string, error) {
pi.MaxSize = pageSize
pi.Token = pageToken
// Get one item, which will fill the buffer.
if err := next(); err != nil {
return "", err
}
// Collect the rest of the buffer.
for pi.Remaining() > 0 {
if err := next(); err != nil {
return "", err
}
}
return pi.Token, nil
}

733
vendor/cloud.google.com/go/firestore/docref_test.go generated vendored Normal file
View File

@@ -0,0 +1,733 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"reflect"
"sort"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/genproto/googleapis/type/latlng"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
var (
writeResultForSet = &WriteResult{UpdateTime: aTime}
commitResponseForSet = &pb.CommitResponse{
WriteResults: []*pb.WriteResult{{UpdateTime: aTimestamp}},
}
)
func TestDocGet(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
path := "projects/projectID/databases/(default)/documents/C/a"
pdoc := &pb.Document{
Name: path,
CreateTime: aTimestamp,
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(1)},
}
srv.addRPC(&pb.GetDocumentRequest{Name: path}, pdoc)
ref := c.Collection("C").Doc("a")
gotDoc, err := ref.Get(ctx)
if err != nil {
t.Fatal(err)
}
wantDoc := &DocumentSnapshot{
Ref: ref,
CreateTime: aTime,
UpdateTime: aTime,
proto: pdoc,
c: c,
}
if !testEqual(gotDoc, wantDoc) {
t.Fatalf("\ngot %+v\nwant %+v", gotDoc, wantDoc)
}
srv.addRPC(
&pb.GetDocumentRequest{
Name: "projects/projectID/databases/(default)/documents/C/b",
},
grpc.Errorf(codes.NotFound, "not found"),
)
_, err = c.Collection("C").Doc("b").Get(ctx)
if grpc.Code(err) != codes.NotFound {
t.Errorf("got %v, want NotFound", err)
}
}
func TestDocSet(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
for _, test := range []struct {
desc string
data interface{}
opt SetOption
write map[string]*pb.Value
mask []string
transform []string
isErr bool
}{
{
desc: "Set with no options",
data: map[string]interface{}{"a": 1},
write: map[string]*pb.Value{"a": intval(1)},
},
{
desc: "Merge with a field",
data: map[string]interface{}{"a": 1, "b": 2},
opt: Merge("a"),
write: map[string]*pb.Value{"a": intval(1)},
mask: []string{"a"},
},
{
desc: "Merge field is not a leaf",
data: map[string]interface{}{
"a": map[string]interface{}{"b": 1, "c": 2},
"d": 3,
},
opt: Merge("a"),
write: map[string]*pb.Value{"a": mapval(map[string]*pb.Value{
"b": intval(1),
"c": intval(2),
})},
mask: []string{"a"},
},
{
desc: "MergeAll",
data: map[string]interface{}{"a": 1, "b": 2},
opt: MergeAll,
write: map[string]*pb.Value{"a": intval(1), "b": intval(2)},
mask: []string{"a", "b"},
},
{
desc: "MergeAll with nested fields",
data: map[string]interface{}{
"a": 1,
"b": map[string]interface{}{"c": 2},
},
opt: MergeAll,
write: map[string]*pb.Value{
"a": intval(1),
"b": mapval(map[string]*pb.Value{"c": intval(2)}),
},
mask: []string{"a", "b.c"},
},
{
desc: "Merge with FieldPaths",
data: map[string]interface{}{"*": map[string]interface{}{"~": true}},
opt: MergePaths([]string{"*", "~"}),
write: map[string]*pb.Value{
"*": mapval(map[string]*pb.Value{
"~": boolval(true),
}),
},
mask: []string{"`*`.`~`"},
},
{
desc: "Merge with a struct and FieldPaths",
data: struct {
A map[string]bool `firestore:"*"`
}{A: map[string]bool{"~": true}},
opt: MergePaths([]string{"*", "~"}),
write: map[string]*pb.Value{
"*": mapval(map[string]*pb.Value{
"~": boolval(true),
}),
},
mask: []string{"`*`.`~`"},
},
{
desc: "a ServerTimestamp field becomes a transform",
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
write: map[string]*pb.Value{"a": intval(1)},
transform: []string{"b"},
},
{
desc: "nested ServerTimestamp field",
data: map[string]interface{}{
"a": 1,
"b": map[string]interface{}{"c": ServerTimestamp},
},
// TODO(jba): make this be map[string]*pb.Value{"a": intval(1)},
write: map[string]*pb.Value{"a": intval(1), "b": mapval(map[string]*pb.Value{})},
transform: []string{"b.c"},
},
{
desc: "multiple ServerTimestamp fields",
data: map[string]interface{}{
"a": 1,
"b": ServerTimestamp,
"c": map[string]interface{}{"d": ServerTimestamp},
},
// TODO(jba): make this be map[string]*pb.Value{"a": intval(1)},
write: map[string]*pb.Value{"a": intval(1),
"c": mapval(map[string]*pb.Value{})},
transform: []string{"b", "c.d"},
},
{
desc: "ServerTimestamp with MergeAll",
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
opt: MergeAll,
write: map[string]*pb.Value{"a": intval(1)},
mask: []string{"a"},
transform: []string{"b"},
},
{
desc: "ServerTimestamp with Merge of both fields",
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
opt: Merge("a", "b"),
write: map[string]*pb.Value{"a": intval(1)},
mask: []string{"a"},
transform: []string{"b"},
},
{
desc: "If is ServerTimestamp not in Merge, no transform",
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
opt: Merge("a"),
write: map[string]*pb.Value{"a": intval(1)},
mask: []string{"a"},
},
{
desc: "If no ordinary values in Merge, no write",
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
opt: Merge("b"),
transform: []string{"b"},
},
{
desc: "Merge fields must all be present in data.",
data: map[string]interface{}{"a": 1},
opt: Merge("b", "a"),
isErr: true,
},
{
desc: "MergeAll cannot be used with structs",
data: struct{ A int }{A: 1},
opt: MergeAll,
isErr: true,
},
{
desc: "Delete cannot appear in data",
data: map[string]interface{}{"a": 1, "b": Delete},
isErr: true,
},
{
desc: "Delete cannot even appear in an unmerged field (allow?)",
data: map[string]interface{}{"a": 1, "b": Delete},
opt: Merge("a"),
isErr: true,
},
} {
srv.reset()
if !test.isErr {
var writes []*pb.Write
if test.write != nil || test.mask != nil {
w := &pb.Write{}
if test.write != nil {
w.Operation = &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: test.write,
},
}
}
if test.mask != nil {
w.UpdateMask = &pb.DocumentMask{FieldPaths: test.mask}
}
writes = append(writes, w)
}
if test.transform != nil {
var fts []*pb.DocumentTransform_FieldTransform
for _, p := range test.transform {
fts = append(fts, &pb.DocumentTransform_FieldTransform{
FieldPath: p,
TransformType: requestTimeTransform,
})
}
writes = append(writes, &pb.Write{
Operation: &pb.Write_Transform{
&pb.DocumentTransform{
Document: "projects/projectID/databases/(default)/documents/C/d",
FieldTransforms: fts,
},
},
})
}
srv.addRPC(&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: writes,
}, commitResponseForSet)
}
var opts []SetOption
if test.opt != nil {
opts = []SetOption{test.opt}
}
wr, err := c.Collection("C").Doc("d").Set(ctx, test.data, opts...)
if test.isErr && err == nil {
t.Errorf("%s: got nil, want error")
continue
}
if !test.isErr && err != nil {
t.Errorf("%s: %v", test.desc, err)
continue
}
if err == nil && !testEqual(wr, writeResultForSet) {
t.Errorf("%s: got %v, want %v", test.desc, wr, writeResultForSet)
}
}
}
func TestDocCreate(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
wantReq := commitRequestForSet()
wantReq.Writes[0].CurrentDocument = &pb.Precondition{
ConditionType: &pb.Precondition_Exists{false},
}
srv.addRPC(wantReq, commitResponseForSet)
wr, err := c.Collection("C").Doc("d").Create(ctx, testData)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %v, want %v", wr, writeResultForSet)
}
// Verify creation with structs. In particular, make sure zero values
// are handled well.
type create struct {
Time time.Time
Bytes []byte
Geo *latlng.LatLng
}
srv.addRPC(
&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: map[string]*pb.Value{
"Time": tsval(time.Time{}),
"Bytes": bytesval(nil),
"Geo": nullValue,
},
},
},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{false},
},
},
},
},
commitResponseForSet,
)
_, err = c.Collection("C").Doc("d").Create(ctx, &create{})
if err != nil {
t.Fatal(err)
}
}
func TestDocDelete(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
srv.addRPC(
&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{Operation: &pb.Write_Delete{"projects/projectID/databases/(default)/documents/C/d"}},
},
},
&pb.CommitResponse{
WriteResults: []*pb.WriteResult{{}},
})
wr, err := c.Collection("C").Doc("d").Delete(ctx)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, &WriteResult{}) {
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
}
}
func TestDocDeleteLastUpdateTime(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
wantReq := &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Delete{"projects/projectID/databases/(default)/documents/C/d"},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_UpdateTime{aTimestamp2},
},
}},
}
srv.addRPC(wantReq, commitResponseForSet)
wr, err := c.Collection("C").Doc("d").Delete(ctx, LastUpdateTime(aTime2))
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
}
}
var (
testData = map[string]interface{}{"a": 1}
testFields = map[string]*pb.Value{"a": intval(1)}
)
func TestUpdateMap(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
for _, test := range []struct {
data map[string]interface{}
wantFields map[string]*pb.Value
wantPaths []string
}{
{
data: map[string]interface{}{"a.b": 1},
wantFields: map[string]*pb.Value{
"a": mapval(map[string]*pb.Value{"b": intval(1)}),
},
wantPaths: []string{"a.b"},
},
{
data: map[string]interface{}{
"a": 1,
"b": Delete,
},
wantFields: map[string]*pb.Value{"a": intval(1)},
wantPaths: []string{"a", "b"},
},
} {
srv.reset()
wantReq := &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: test.wantFields,
}},
UpdateMask: &pb.DocumentMask{FieldPaths: test.wantPaths},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
}},
}
// Sort update masks, because map iteration order is random.
sort.Strings(wantReq.Writes[0].UpdateMask.FieldPaths)
srv.addRPCAdjust(wantReq, commitResponseForSet, func(gotReq proto.Message) {
sort.Strings(gotReq.(*pb.CommitRequest).Writes[0].UpdateMask.FieldPaths)
})
wr, err := c.Collection("C").Doc("d").UpdateMap(ctx, test.data)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("%v:\ngot %+v, want %+v", test.data, wr, writeResultForSet)
}
}
}
func TestUpdateMapLastUpdateTime(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
wantReq := &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: map[string]*pb.Value{"a": intval(1)},
}},
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"a"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_UpdateTime{aTimestamp2},
},
}},
}
srv.addRPC(wantReq, commitResponseForSet)
wr, err := c.Collection("C").Doc("d").UpdateMap(ctx, map[string]interface{}{"a": 1}, LastUpdateTime(aTime2))
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %v, want %v", wr, writeResultForSet)
}
}
func TestUpdateMapErrors(t *testing.T) {
ctx := context.Background()
c, _ := newMock(t)
for _, in := range []map[string]interface{}{
nil, // no paths
map[string]interface{}{"a~b": 1}, // invalid character
map[string]interface{}{"a..b": 1}, // empty path component
map[string]interface{}{"a.b": 1, "a": 2}, // prefix
} {
_, err := c.Collection("C").Doc("d").UpdateMap(ctx, in)
if err == nil {
t.Errorf("%v: got nil, want error", in)
}
}
}
func TestUpdateStruct(t *testing.T) {
type update struct{ A int }
c, srv := newMock(t)
wantReq := &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: map[string]*pb.Value{"A": intval(2)},
},
},
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"A", "b.c"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
}},
}
srv.addRPC(wantReq, commitResponseForSet)
wr, err := c.Collection("C").Doc("d").
UpdateStruct(context.Background(), []string{"A", "b.c"}, &update{A: 2})
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
}
}
func TestUpdateStructErrors(t *testing.T) {
type update struct{ A int }
ctx := context.Background()
c, _ := newMock(t)
doc := c.Collection("C").Doc("d")
for _, test := range []struct {
desc string
fields []string
data interface{}
}{
{
desc: "data is not a struct or *struct",
data: map[string]interface{}{"a": 1},
},
{
desc: "no paths",
fields: nil,
data: update{},
},
{
desc: "empty",
fields: []string{""},
data: update{},
},
{
desc: "empty component",
fields: []string{"a.b..c"},
data: update{},
},
{
desc: "duplicate field",
fields: []string{"a", "b", "c", "a"},
data: update{},
},
{
desc: "invalid character",
fields: []string{"a", "b]"},
data: update{},
},
{
desc: "prefix",
fields: []string{"a", "b", "c", "b.c"},
data: update{},
},
} {
_, err := doc.UpdateStruct(ctx, test.fields, test.data)
if err == nil {
t.Errorf("%s: got nil, want error", test.desc)
}
}
}
func TestUpdatePaths(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
for _, test := range []struct {
data []FieldPathUpdate
wantFields map[string]*pb.Value
wantPaths []string
}{
{
data: []FieldPathUpdate{
{Path: []string{"*", "~"}, Value: 1},
{Path: []string{"*", "/"}, Value: 2},
},
wantFields: map[string]*pb.Value{
"*": mapval(map[string]*pb.Value{
"~": intval(1),
"/": intval(2),
}),
},
wantPaths: []string{"`*`.`~`", "`*`.`/`"},
},
{
data: []FieldPathUpdate{
{Path: []string{"*"}, Value: 1},
{Path: []string{"]"}, Value: Delete},
},
wantFields: map[string]*pb.Value{"*": intval(1)},
wantPaths: []string{"`*`", "`]`"},
},
} {
srv.reset()
wantReq := &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: test.wantFields,
}},
UpdateMask: &pb.DocumentMask{FieldPaths: test.wantPaths},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
}},
}
// Sort update masks, because map iteration order is random.
sort.Strings(wantReq.Writes[0].UpdateMask.FieldPaths)
srv.addRPCAdjust(wantReq, commitResponseForSet, func(gotReq proto.Message) {
sort.Strings(gotReq.(*pb.CommitRequest).Writes[0].UpdateMask.FieldPaths)
})
wr, err := c.Collection("C").Doc("d").UpdatePaths(ctx, test.data)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("%v:\ngot %+v, want %+v", test.data, wr, writeResultForSet)
}
}
}
func TestUpdatePathsErrors(t *testing.T) {
fpu := func(s ...string) FieldPathUpdate { return FieldPathUpdate{Path: s} }
ctx := context.Background()
c, _ := newMock(t)
doc := c.Collection("C").Doc("d")
for _, test := range []struct {
desc string
data []FieldPathUpdate
}{
{"no updates", nil},
{"empty", []FieldPathUpdate{fpu("")}},
{"empty component", []FieldPathUpdate{fpu("*", "")}},
{"duplicate field", []FieldPathUpdate{fpu("~"), fpu("*"), fpu("~")}},
{"prefix", []FieldPathUpdate{fpu("*", "a"), fpu("b"), fpu("*", "a", "b")}},
} {
_, err := doc.UpdatePaths(ctx, test.data)
if err == nil {
t.Errorf("%s: got nil, want error", test.desc)
}
}
}
func TestApplyFieldPaths(t *testing.T) {
submap := mapval(map[string]*pb.Value{
"b": intval(1),
"c": intval(2),
})
fields := map[string]*pb.Value{
"a": submap,
"d": intval(3),
}
for _, test := range []struct {
fps []FieldPath
want map[string]*pb.Value
}{
{nil, nil},
{[]FieldPath{[]string{"z"}}, nil},
{[]FieldPath{[]string{"a"}}, map[string]*pb.Value{"a": submap}},
{[]FieldPath{[]string{"a", "b", "c"}}, nil},
{[]FieldPath{[]string{"d"}}, map[string]*pb.Value{"d": intval(3)}},
{
[]FieldPath{[]string{"d"}, []string{"a", "c"}},
map[string]*pb.Value{
"a": mapval(map[string]*pb.Value{"c": intval(2)}),
"d": intval(3),
},
},
} {
got := applyFieldPaths(fields, test.fps, nil)
if !testEqual(got, test.want) {
t.Errorf("%v:\ngot %v\nwant \n%v", test.fps, got, test.want)
}
}
}
func TestFieldPathsFromMap(t *testing.T) {
for _, test := range []struct {
in map[string]interface{}
want []string
}{
{nil, nil},
{map[string]interface{}{"a": 1}, []string{"a"}},
{map[string]interface{}{
"a": 1,
"b": map[string]interface{}{"c": 2},
}, []string{"a", "b.c"}},
} {
fps := fieldPathsFromMap(reflect.ValueOf(test.in), nil)
got := toServiceFieldPaths(fps)
sort.Strings(got)
if !testEqual(got, test.want) {
t.Errorf("%+v: got %v, want %v", test.in, got, test.want)
}
}
}
func commitRequestForSet() *pb.CommitRequest {
return &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: testFields,
},
},
},
},
}
}

266
vendor/cloud.google.com/go/firestore/document.go generated vendored Normal file
View File

@@ -0,0 +1,266 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"reflect"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
)
// A DocumentSnapshot contains document data and metadata.
type DocumentSnapshot struct {
// The DocumentRef for this document.
Ref *DocumentRef
// Read-only. The time at which the document was created.
// Increases monotonically when a document is deleted then
// recreated. It can also be compared to values from other documents and
// the read time of a query.
CreateTime time.Time
// Read-only. The time at which the document was last changed. This value
// is initally set to CreateTime then increases monotonically with each
// change to the document. It can also be compared to values from other
// documents and the read time of a query.
UpdateTime time.Time
c *Client
proto *pb.Document
}
func (d1 *DocumentSnapshot) equal(d2 *DocumentSnapshot) bool {
if d1 == nil || d2 == nil {
return d1 == d2
}
return d1.Ref.equal(d2.Ref) &&
d1.CreateTime.Equal(d2.CreateTime) &&
d1.UpdateTime.Equal(d2.UpdateTime) &&
d1.c == d2.c &&
proto.Equal(d1.proto, d2.proto)
}
// Data returns the DocumentSnapshot's fields as a map.
// It is equivalent to
// var m map[string]interface{}
// d.DataTo(&m)
func (d *DocumentSnapshot) Data() map[string]interface{} {
m, err := createMapFromValueMap(d.proto.Fields, d.c)
// Any error here is a bug in the client.
if err != nil {
panic(fmt.Sprintf("firestore: %v", err))
}
return m
}
// DataTo uses the document's fields to populate p, which can be a pointer to a
// map[string]interface{} or a pointer to a struct.
//
// Firestore field values are converted to Go values as follows:
// - Null converts to nil.
// - Bool converts to bool.
// - String converts to string.
// - Integer converts int64. When setting a struct field, any signed or unsigned
// integer type is permitted except uint64. Overflow is detected and results in
// an error.
// - Double converts to float64. When setting a struct field, float32 is permitted.
// Overflow is detected and results in an error.
// - Bytes is converted to []byte.
// - Timestamp converts to time.Time.
// - GeoPoint converts to latlng.LatLng, where latlng is the package
// "google.golang.org/genproto/googleapis/type/latlng".
// - Arrays convert to []interface{}. When setting a struct field, the field
// may be a slice or array of any type and is populated recursively.
// Slices are resized to the incoming value's size, while arrays that are too
// long have excess elements filled with zero values. If the array is too short,
// excess incoming values will be dropped.
// - Maps convert to map[string]interface{}. When setting a struct field,
// maps of key type string and any value type are permitted, and are populated
// recursively.
// - References are converted to DocumentRefs.
//
// Field names given by struct field tags are observed, as described in
// DocumentRef.Create.
func (d *DocumentSnapshot) DataTo(p interface{}) error {
return setFromProtoValue(p, &pb.Value{&pb.Value_MapValue{&pb.MapValue{d.proto.Fields}}}, d.c)
}
// DataAt returns the data value denoted by fieldPath.
//
// The fieldPath argument can be a single field or a dot-separated sequence of
// fields, and must not contain any of the runes "˜*/[]". Use DataAtPath instead for
// such a path.
//
// See DocumentSnapshot.DataTo for how Firestore values are converted to Go values.
func (d *DocumentSnapshot) DataAt(fieldPath string) (interface{}, error) {
fp, err := parseDotSeparatedString(fieldPath)
if err != nil {
return nil, err
}
return d.DataAtPath(fp)
}
// DataAtPath returns the data value denoted by the FieldPath fp.
func (d *DocumentSnapshot) DataAtPath(fp FieldPath) (interface{}, error) {
v, err := valueAtPath(fp, d.proto.Fields)
if err != nil {
return nil, err
}
return createFromProtoValue(v, d.c)
}
// valueAtPath returns the value of m referred to by fp.
func valueAtPath(fp FieldPath, m map[string]*pb.Value) (*pb.Value, error) {
for _, k := range fp[:len(fp)-1] {
v := m[k]
if v == nil {
return nil, fmt.Errorf("firestore: no field %q", k)
}
mv := v.GetMapValue()
if mv == nil {
return nil, fmt.Errorf("firestore: value for field %q is not a map", k)
}
m = mv.Fields
}
k := fp[len(fp)-1]
v := m[k]
if v == nil {
return nil, fmt.Errorf("firestore: no field %q", k)
}
return v, nil
}
// toProtoDocument converts a Go value to a Document proto.
// Valid values are: map[string]T, struct, or pointer to a valid value.
// It also returns a list of field paths for DocumentTransform (server timestamp).
func toProtoDocument(x interface{}) (*pb.Document, []FieldPath, error) {
if x == nil {
return nil, nil, errors.New("firestore: nil document contents")
}
v := reflect.ValueOf(x)
pv, err := toProtoValue(v)
if err != nil {
return nil, nil, err
}
fieldPaths, err := extractTransformPaths(v, nil)
if err != nil {
return nil, nil, err
}
m := pv.GetMapValue()
if m == nil {
return nil, nil, fmt.Errorf("firestore: cannot covert value of type %T into a map", x)
}
return &pb.Document{Fields: m.Fields}, fieldPaths, nil
}
func extractTransformPaths(v reflect.Value, prefix FieldPath) ([]FieldPath, error) {
switch v.Kind() {
case reflect.Map:
return extractTransformPathsFromMap(v, prefix)
case reflect.Struct:
return extractTransformPathsFromStruct(v, prefix)
case reflect.Ptr:
if v.IsNil() {
return nil, nil
}
return extractTransformPaths(v.Elem(), prefix)
case reflect.Interface:
if v.NumMethod() == 0 { // empty interface: recurse on its contents
return extractTransformPaths(v.Elem(), prefix)
}
return nil, nil
default:
return nil, nil
}
}
func extractTransformPathsFromMap(v reflect.Value, prefix FieldPath) ([]FieldPath, error) {
var paths []FieldPath
for _, k := range v.MapKeys() {
sk := k.Interface().(string) // assume keys are strings; checked in toProtoValue
path := prefix.with(sk)
mi := v.MapIndex(k)
if mi.Interface() == ServerTimestamp {
paths = append(paths, path)
} else {
ps, err := extractTransformPaths(mi, path)
if err != nil {
return nil, err
}
paths = append(paths, ps...)
}
}
return paths, nil
}
func extractTransformPathsFromStruct(v reflect.Value, prefix FieldPath) ([]FieldPath, error) {
var paths []FieldPath
fields, err := fieldCache.Fields(v.Type())
if err != nil {
return nil, err
}
for _, f := range fields {
fv := v.FieldByIndex(f.Index)
path := prefix.with(f.Name)
opts := f.ParsedTag.(tagOptions)
if opts.serverTimestamp {
var isZero bool
switch f.Type {
case typeOfGoTime:
isZero = fv.Interface().(time.Time).IsZero()
case reflect.PtrTo(typeOfGoTime):
isZero = fv.IsNil() || fv.Elem().Interface().(time.Time).IsZero()
default:
return nil, fmt.Errorf("firestore: field %s of struct %s with serverTimestamp tag must be of type time.Time or *time.Time",
f.Name, v.Type())
}
if isZero {
paths = append(paths, path)
}
} else {
ps, err := extractTransformPaths(fv, path)
if err != nil {
return nil, err
}
paths = append(paths, ps...)
}
}
return paths, nil
}
func newDocumentSnapshot(ref *DocumentRef, proto *pb.Document, c *Client) (*DocumentSnapshot, error) {
d := &DocumentSnapshot{
Ref: ref,
c: c,
proto: proto,
}
ts, err := ptypes.Timestamp(proto.CreateTime)
if err != nil {
return nil, err
}
d.CreateTime = ts
ts, err = ptypes.Timestamp(proto.UpdateTime)
if err != nil {
return nil, err
}
d.UpdateTime = ts
return d, nil
}

238
vendor/cloud.google.com/go/firestore/document_test.go generated vendored Normal file
View File

@@ -0,0 +1,238 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"reflect"
"sort"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
tspb "github.com/golang/protobuf/ptypes/timestamp"
)
func TestToProtoDocument(t *testing.T) {
type s struct{ I int }
for _, test := range []struct {
in interface{}
want *pb.Document
wantErr bool
}{
{nil, nil, true},
{[]int{1}, nil, true},
{map[string]int{"a": 1},
&pb.Document{Fields: map[string]*pb.Value{"a": intval(1)}},
false},
{s{2}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(2)}}, false},
{&s{3}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(3)}}, false},
} {
got, _, gotErr := toProtoDocument(test.in)
if (gotErr != nil) != test.wantErr {
t.Errorf("%v: got error %v, want %t", test.in, gotErr, test.wantErr)
}
if gotErr != nil {
continue
}
if !testEqual(got, test.want) {
t.Errorf("%v: got %v, want %v", test.in, got, test.want)
}
}
}
func TestNewDocumentSnapshot(t *testing.T) {
c := &Client{
projectID: "projID",
databaseID: "(database)",
}
docRef := c.Doc("C/a")
in := &pb.Document{
CreateTime: &tspb.Timestamp{Seconds: 10},
UpdateTime: &tspb.Timestamp{Seconds: 20},
Fields: map[string]*pb.Value{"a": intval(1)},
}
want := &DocumentSnapshot{
Ref: docRef,
CreateTime: time.Unix(10, 0).UTC(),
UpdateTime: time.Unix(20, 0).UTC(),
proto: in,
c: c,
}
got, err := newDocumentSnapshot(docRef, in, c)
if err != nil {
t.Fatal(err)
}
if !testEqual(got, want) {
t.Errorf("got %+v\nwant %+v", got, want)
}
}
func TestData(t *testing.T) {
doc := &DocumentSnapshot{
proto: &pb.Document{
Fields: map[string]*pb.Value{"a": intval(1), "b": strval("x")},
},
}
got := doc.Data()
want := map[string]interface{}{"a": int64(1), "b": "x"}
if !testEqual(got, want) {
t.Errorf("got %#v\nwant %#v", got, want)
}
var got2 map[string]interface{}
if err := doc.DataTo(&got2); err != nil {
t.Fatal(err)
}
if !testEqual(got2, want) {
t.Errorf("got %#v\nwant %#v", got2, want)
}
type s struct {
A int
B string
}
var got3 s
if err := doc.DataTo(&got3); err != nil {
t.Fatal(err)
}
want2 := s{A: 1, B: "x"}
if !testEqual(got3, want2) {
t.Errorf("got %#v\nwant %#v", got3, want2)
}
}
var testDoc = &DocumentSnapshot{
proto: &pb.Document{
Fields: map[string]*pb.Value{
"a": intval(1),
"b": mapval(map[string]*pb.Value{
"`": intval(2),
"~": mapval(map[string]*pb.Value{
"x": intval(3),
}),
}),
},
},
}
func TestDataAt(t *testing.T) {
for _, test := range []struct {
fieldPath string
want interface{}
}{
{"a", int64(1)},
{"b.`", int64(2)},
} {
got, err := testDoc.DataAt(test.fieldPath)
if err != nil {
t.Errorf("%q: %v", test.fieldPath, err)
continue
}
if !testEqual(got, test.want) {
t.Errorf("%q: got %v, want %v", test.fieldPath, got, test.want)
}
}
for _, bad := range []string{
"c.~.x", // bad field path
"a.b", // "a" isn't a map
"z.b", // bad non-final key
"b.z", // bad final key
} {
_, err := testDoc.DataAt(bad)
if err == nil {
t.Errorf("%q: got nil, want error", bad)
}
}
}
func TestDataAtPath(t *testing.T) {
for _, test := range []struct {
fieldPath FieldPath
want interface{}
}{
{[]string{"a"}, int64(1)},
{[]string{"b", "`"}, int64(2)},
{[]string{"b", "~"}, map[string]interface{}{"x": int64(3)}},
{[]string{"b", "~", "x"}, int64(3)},
} {
got, err := testDoc.DataAtPath(test.fieldPath)
if err != nil {
t.Errorf("%v: %v", test.fieldPath, err)
continue
}
if !testEqual(got, test.want) {
t.Errorf("%v: got %v, want %v", test.fieldPath, got, test.want)
}
}
for _, bad := range []FieldPath{
[]string{"c", "", "x"}, // bad field path
[]string{"a", "b"}, // "a" isn't a map
[]string{"z", "~"}, // bad non-final key
[]string{"b", "z"}, // bad final key
} {
_, err := testDoc.DataAtPath(bad)
if err == nil {
t.Errorf("%v: got nil, want error", bad)
}
}
}
func TestExtractTransformPaths(t *testing.T) {
type S struct {
A time.Time `firestore:",serverTimestamp"`
B time.Time `firestore:",serverTimestamp"`
C *time.Time `firestore:",serverTimestamp"`
D *time.Time `firestore:"d.d,serverTimestamp"`
E *time.Time `firestore:",serverTimestamp"`
F time.Time
G int
}
m := map[string]interface{}{
"x": 1,
"y": &S{
// A is a zero time: included
B: aTime, // not a zero time: excluded
// C is nil: included
D: &time.Time{}, // pointer to a zero time: included
E: &aTime, // pointer to a non-zero time: excluded
// F is a zero time, but does not have the right tag: excluded
G: 15, // not a time.Time
},
"z": map[string]interface{}{"w": ServerTimestamp},
}
got, err := extractTransformPaths(reflect.ValueOf(m), nil)
if err != nil {
t.Fatal(err)
}
sort.Sort(byPath(got))
want := []FieldPath{{"y", "A"}, {"y", "C"}, {"y", "d.d"}, {"z", "w"}}
if !testEqual(got, want) {
t.Errorf("got %#v, want %#v", got, want)
}
}
func TestExtractTransformPathsErrors(t *testing.T) {
type S struct {
A int `firestore:",serverTimestamp"`
}
_, err := extractTransformPaths(reflect.ValueOf(S{}), nil)
if err == nil {
t.Error("got nil, want error")
}
}

552
vendor/cloud.google.com/go/firestore/examples_test.go generated vendored Normal file
View File

@@ -0,0 +1,552 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(jba): add Output comments to examples when feasible.
package firestore_test
import (
"fmt"
"cloud.google.com/go/firestore"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
)
func ExampleNewClient() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close() // Close client when done.
_ = client // TODO: Use client.
}
func ExampleClient_Collection() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
coll1 := client.Collection("States")
coll2 := client.Collection("States/NewYork/Cities")
fmt.Println(coll1, coll2)
}
func ExampleClient_Doc() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
doc1 := client.Doc("States/NewYork")
doc2 := client.Doc("States/NewYork/Cities/Albany")
fmt.Println(doc1, doc2)
}
func ExampleClient_GetAll() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docs, err := client.GetAll(ctx, []*firestore.DocumentRef{
client.Doc("States/NorthCarolina"),
client.Doc("States/SouthCarolina"),
client.Doc("States/WestCarolina"),
client.Doc("States/EastCarolina"),
})
if err != nil {
// TODO: Handle error.
}
// docs is a slice with four DocumentSnapshots, but the last two are
// nil because there is no West or East Carolina.
fmt.Println(docs)
}
func ExampleClient_Batch() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
b := client.Batch()
_ = b // TODO: Use batch.
}
func ExampleWriteBatch_Commit() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
ny := client.Doc("States/NewYork")
ca := client.Doc("States/California")
writeResults, err := client.Batch().
Create(ny, State{Capital: "Albany", Population: 19.8}).
Set(ca, State{Capital: "Sacramento", Population: 39.14}).
Delete(client.Doc("States/WestDakota")).
Commit(ctx)
if err != nil {
// TODO: Handle error.
}
fmt.Println(writeResults)
}
func ExampleCollectionRef_Add() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
doc, wr, err := client.Collection("Users").Add(ctx, map[string]interface{}{
"name": "Alice",
"email": "aj@example.com",
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(doc, wr)
}
func ExampleCollectionRef_Doc() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
fl := client.Collection("States").Doc("Florida")
ta := client.Collection("States").Doc("Florida/Cities/Tampa")
fmt.Println(fl, ta)
}
func ExampleCollectionRef_NewDoc() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
doc := client.Collection("Users").NewDoc()
fmt.Println(doc)
}
func ExampleDocumentRef_Collection() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
mi := client.Collection("States").Doc("Michigan")
cities := mi.Collection("Cities")
fmt.Println(cities)
}
func ExampleDocumentRef_Create_map() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
wr, err := client.Doc("States/Colorado").Create(ctx, map[string]interface{}{
"capital": "Denver",
"pop": 5.5,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_Create_struct() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
wr, err := client.Doc("States/Colorado").Create(ctx, State{
Capital: "Denver",
Population: 5.5,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_Set() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
// Overwrite the document with the given data. Any other fields currently
// in the document will be removed.
wr, err := client.Doc("States/Alabama").Set(ctx, map[string]interface{}{
"capital": "Montgomery",
"pop": 4.9,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_Set_merge() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
// Overwrite only the fields in the map; preserve all others.
_, err = client.Doc("States/Alabama").Set(ctx, map[string]interface{}{
"pop": 5.2,
}, firestore.MergeAll)
if err != nil {
// TODO: Handle error.
}
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
// To do a merging Set with struct data, specify the exact fields to overwrite.
// MergeAll is disallowed here, because it would probably be a mistake: the "capital"
// field would be overwritten with the empty string.
_, err = client.Doc("States/Alabama").Set(ctx, State{Population: 5.2}, firestore.Merge("pop"))
if err != nil {
// TODO: Handle error.
}
}
func ExampleDocumentRef_UpdateMap() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
tenn := client.Doc("States/Tennessee")
wr, err := tenn.UpdateMap(ctx, map[string]interface{}{"pop": 6.6})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_UpdateStruct() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
tenn := client.Doc("States/Tennessee")
wr, err := tenn.UpdateStruct(ctx, []string{"pop"}, State{
Capital: "does not matter",
Population: 6.6,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_UpdatePaths() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
tenn := client.Doc("States/Tennessee")
wr, err := tenn.UpdatePaths(ctx, []firestore.FieldPathUpdate{
{Path: []string{"pop"}, Value: 6.6},
// This odd field path cannot be expressed using the dot-separated form:
{Path: []string{".", "*", "/"}, Value: "odd"},
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
}
func ExampleDocumentRef_Delete() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
// Oops, Ontario is a Canadian province...
if _, err = client.Doc("States/Ontario").Delete(ctx); err != nil {
// TODO: Handle error.
}
}
func ExampleDocumentRef_Get() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docsnap, err := client.Doc("States/Ohio").Get(ctx)
if err != nil {
// TODO: Handle error.
}
_ = docsnap // TODO: Use DocumentSnapshot.
}
func ExampleDocumentSnapshot_Data() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docsnap, err := client.Doc("States/Ohio").Get(ctx)
if err != nil {
// TODO: Handle error.
}
ohioMap := docsnap.Data()
fmt.Println(ohioMap["capital"])
}
func ExampleDocumentSnapshot_DataAt() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docsnap, err := client.Doc("States/Ohio").Get(ctx)
if err != nil {
// TODO: Handle error.
}
cap, err := docsnap.DataAt("capital")
if err != nil {
// TODO: Handle error.
}
fmt.Println(cap)
}
func ExampleDocumentSnapshot_DataAtPath() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docsnap, err := client.Doc("States/Ohio").Get(ctx)
if err != nil {
// TODO: Handle error.
}
pop, err := docsnap.DataAtPath([]string{"capital", "population"})
if err != nil {
// TODO: Handle error.
}
fmt.Println(pop)
}
func ExampleDocumentSnapshot_DataTo() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
docsnap, err := client.Doc("States/Ohio").Get(ctx)
if err != nil {
// TODO: Handle error.
}
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
var s State
if err := docsnap.DataTo(&s); err != nil {
// TODO: Handle error.
}
fmt.Println(s)
}
func ExampleQuery_Documents() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
q := client.Collection("States").Select("pop").
Where("pop", ">", 10).
OrderBy("pop", firestore.Desc).
Limit(10)
iter1 := q.Documents(ctx)
_ = iter1 // TODO: Use iter1.
// You can call Documents directly on a CollectionRef as well.
iter2 := client.Collection("States").Documents(ctx)
_ = iter2 // TODO: Use iter2.
}
// This example is just like the one above, but illustrates
// how to use the XXXPath methods of Query for field paths
// that can't be expressed as a dot-separated string.
func ExampleQuery_Documents_path_methods() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
q := client.Collection("Unusual").SelectPaths([]string{"*"}, []string{"[~]"}).
WherePath([]string{"/"}, ">", 10).
OrderByPath([]string{"/"}, firestore.Desc).
Limit(10)
iter1 := q.Documents(ctx)
_ = iter1 // TODO: Use iter1.
// You can call Documents directly on a CollectionRef as well.
iter2 := client.Collection("States").Documents(ctx)
_ = iter2 // TODO: Use iter2.
}
func ExampleDocumentIterator_Next() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
q := client.Collection("States").
Where("pop", ">", 10).
OrderBy("pop", firestore.Desc)
iter := q.Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(doc.Data())
}
}
func ExampleDocumentIterator_GetAll() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
q := client.Collection("States").
Where("pop", ">", 10).
OrderBy("pop", firestore.Desc).
Limit(10) // a good idea with GetAll, to avoid filling memory
docs, err := q.Documents(ctx).GetAll()
if err != nil {
// TODO: Handle error.
}
for _, doc := range docs {
fmt.Println(doc.Data())
}
}
func ExampleClient_RunTransaction() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
nm := client.Doc("States/NewMexico")
err = client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(nm) // tx.Get, NOT nm.Get!
if err != nil {
return err
}
pop, err := doc.DataAt("pop")
if err != nil {
return err
}
return tx.UpdateMap(nm, map[string]interface{}{
"pop": pop.(float64) + 0.2,
})
})
if err != nil {
// TODO: Handle error.
}
}

222
vendor/cloud.google.com/go/firestore/fieldpath.go generated vendored Normal file
View File

@@ -0,0 +1,222 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"bytes"
"errors"
"fmt"
"regexp"
"sort"
"strings"
)
// A FieldPath is a non-empty sequence of non-empty fields that reference a value.
//
// A FieldPath value should only be necessary if one of the field names contains
// one of the runes ".˜*/[]". Most methods accept a simpler form of field path
// as a string in which the individual fields are separated by dots.
// For example,
// []string{"a", "b"}
// is equivalent to the string form
// "a.b"
// but
// []string{"*"}
// has no equivalent string form.
type FieldPath []string
// parseDotSeparatedString constructs a FieldPath from a string that separates
// path components with dots. Other than splitting at dots and checking for invalid
// characters, it ignores everything else about the string,
// including attempts to quote field path compontents. So "a.`b.c`.d" is parsed into
// four parts, "a", "`b", "c`" and "d".
func parseDotSeparatedString(s string) (FieldPath, error) {
const invalidRunes = "~*/[]"
if strings.ContainsAny(s, invalidRunes) {
return nil, fmt.Errorf("firestore: %q contains an invalid rune (one of %s)", s, invalidRunes)
}
fp := FieldPath(strings.Split(s, "."))
if err := fp.validate(); err != nil {
return nil, err
}
return fp, nil
}
func parseDotSeparatedStrings(strs []string) ([]FieldPath, error) {
var fps []FieldPath
for _, s := range strs {
fp, err := parseDotSeparatedString(s)
if err != nil {
return nil, err
}
fps = append(fps, fp)
}
return fps, nil
}
func (fp1 FieldPath) equal(fp2 FieldPath) bool {
if len(fp1) != len(fp2) {
return false
}
for i, c1 := range fp1 {
if c1 != fp2[i] {
return false
}
}
return true
}
func (fp1 FieldPath) prefixOf(fp2 FieldPath) bool {
return len(fp1) <= len(fp2) && fp1.equal(fp2[:len(fp1)])
}
// Lexicographic ordering.
func (fp1 FieldPath) less(fp2 FieldPath) bool {
for i := range fp1 {
switch {
case i >= len(fp2):
return false
case fp1[i] < fp2[i]:
return true
case fp1[i] > fp2[i]:
return false
}
}
// fp1 and fp2 are equal up to len(fp1).
return len(fp1) < len(fp2)
}
// validate checks the validity of fp and returns an error if it is invalid.
func (fp FieldPath) validate() error {
if len(fp) == 0 {
return errors.New("firestore: empty field path")
}
for _, c := range fp {
if len(c) == 0 {
return errors.New("firestore: empty component in field path")
}
}
return nil
}
// with creates a new FieldPath consisting of fp followed by k.
func (fp FieldPath) with(k string) FieldPath {
r := make(FieldPath, len(fp), len(fp)+1)
copy(r, fp)
return append(r, k)
}
// in reports whether fp is equal to one of the fps.
func (fp FieldPath) in(fps []FieldPath) bool {
for _, e := range fps {
if fp.equal(e) {
return true
}
}
return false
}
// checkNoDupOrPrefix checks whether any FieldPath is a prefix of (or equal to)
// another.
// It modifies the order of FieldPaths in its argument (via sorting).
func checkNoDupOrPrefix(fps []FieldPath) error {
// Sort fps lexicographically.
sort.Sort(byPath(fps))
// Check adjacent pairs for prefix.
for i := 1; i < len(fps); i++ {
if fps[i-1].prefixOf(fps[i]) {
return fmt.Errorf("field path %v cannot be used in the same update as %v", fps[i-1], fps[i])
}
}
return nil
}
type byPath []FieldPath
func (b byPath) Len() int { return len(b) }
func (b byPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byPath) Less(i, j int) bool { return b[i].less(b[j]) }
// createMapFromFieldPathUpdates uses fpus to construct a valid Firestore data value
// in the form of a map. It assumes the FieldPaths in fpus have already been
// validated and checked for prefixes. If any field path is associated with the
// Delete value, it is not stored in the map.
func createMapFromFieldPathUpdates(fpus []FieldPathUpdate) map[string]interface{} {
m := map[string]interface{}{}
for _, fpu := range fpus {
if fpu.Value != Delete {
setAtPath(m, fpu.Path, fpu.Value)
}
}
return m
}
// setAtPath sets val at the location in m specified by fp, creating sub-maps as
// needed. m must not be nil. fp is assumed to be valid.
func setAtPath(m map[string]interface{}, fp FieldPath, val interface{}) {
if len(fp) == 1 {
m[fp[0]] = val
} else {
v, ok := m[fp[0]]
if !ok {
v = map[string]interface{}{}
m[fp[0]] = v
}
// The type assertion below cannot fail, because setAtPath is only called
// with either an empty map or one filled by setAtPath itself, and the
// set of FieldPaths it is called with has been checked to make sure that
// no path is the prefix of any other.
setAtPath(v.(map[string]interface{}), fp[1:], val)
}
}
// toServiceFieldPath converts fp the form required by the Firestore service.
// It assumes fp has been validated.
func (fp FieldPath) toServiceFieldPath() string {
cs := make([]string, len(fp))
for i, c := range fp {
cs[i] = toServiceFieldPathComponent(c)
}
return strings.Join(cs, ".")
}
func toServiceFieldPaths(fps []FieldPath) []string {
var sfps []string
for _, fp := range fps {
sfps = append(sfps, fp.toServiceFieldPath())
}
return sfps
}
// Google SQL syntax for an unquoted field.
var unquotedFieldRegexp = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$")
// toServiceFieldPathComponent returns a string that represents key and is a valid
// field path component.
func toServiceFieldPathComponent(key string) string {
if unquotedFieldRegexp.MatchString(key) {
return key
}
var buf bytes.Buffer
buf.WriteRune('`')
for _, r := range key {
if r == '`' || r == '\\' {
buf.WriteRune('\\')
}
buf.WriteRune(r)
}
buf.WriteRune('`')
return buf.String()
}

152
vendor/cloud.google.com/go/firestore/fieldpath_test.go generated vendored Normal file
View File

@@ -0,0 +1,152 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"strings"
"testing"
)
func TestFieldPathValidate(t *testing.T) {
for _, in := range [][]string{nil, []string{}, []string{"a", "", "b"}} {
if err := FieldPath(in).validate(); err == nil {
t.Errorf("%v: want error, got nil", in)
}
}
}
func TestFieldPathLess(t *testing.T) {
for _, test := range []struct {
in1, in2 string
want bool
}{
{"a b", "a b", false},
{"a", "b", true},
{"b", "a", false},
{"a", "a b", true},
{"a b", "a", false},
{"a b c", "a b d", true},
{"a b d", "a b c", false},
} {
fp1 := FieldPath(strings.Fields(test.in1))
fp2 := FieldPath(strings.Fields(test.in2))
got := fp1.less(fp2)
if got != test.want {
t.Errorf("%q.less(%q): got %t, want %t", test.in1, test.in2, got, test.want)
}
}
}
func TestCheckForPrefix(t *testing.T) {
for _, test := range []struct {
in []string // field paths as space-separated strings
wantErr bool
}{
{in: []string{"a", "b", "c"}, wantErr: false},
{in: []string{"a b", "b", "c d"}, wantErr: false},
{in: []string{"a b", "a c", "a d"}, wantErr: false},
{in: []string{"a b", "b", "b d"}, wantErr: true},
{in: []string{"a b", "b", "b d"}, wantErr: true},
{in: []string{"b c d", "c d", "b c"}, wantErr: true},
} {
var fps []FieldPath
for _, s := range test.in {
fps = append(fps, strings.Fields(s))
}
err := checkNoDupOrPrefix(fps)
if got, want := (err != nil), test.wantErr; got != want {
t.Errorf("%#v: got '%v', want %t", test.in, err, want)
}
}
}
// Convenience function for creating a FieldPathUpdate.
func fpu(val int, fields ...string) FieldPathUpdate {
return FieldPathUpdate{Path: fields, Value: val}
}
func TestCreateMapFromFieldPathUpdates(t *testing.T) {
type M map[string]interface{}
for _, test := range []struct {
in []FieldPathUpdate
want M
}{
{
in: nil,
want: M{},
},
{
in: []FieldPathUpdate{fpu(1, "a"), fpu(2, "b")},
want: M{"a": 1, "b": 2},
},
{
in: []FieldPathUpdate{fpu(1, "a", "b"), fpu(2, "c")},
want: M{"a": map[string]interface{}{"b": 1}, "c": 2},
},
{
in: []FieldPathUpdate{fpu(1, "a", "b"), fpu(2, "c", "d")},
want: M{
"a": map[string]interface{}{"b": 1},
"c": map[string]interface{}{"d": 2},
},
},
{
in: []FieldPathUpdate{fpu(1, "a", "b"), fpu(2, "a", "c")},
want: M{"a": map[string]interface{}{"b": 1, "c": 2}},
},
} {
gotm := createMapFromFieldPathUpdates(test.in)
got := M(gotm)
if !testEqual(got, test.want) {
t.Errorf("%v: got %#v, want %#v", test.in, got, test.want)
}
}
}
func TestToServiceFieldPath(t *testing.T) {
for _, test := range []struct {
in FieldPath
want string
}{
{[]string{"a"}, "a"},
{[]string{"a", "b"}, "a.b"},
{[]string{"a.", "[b*", "c2"}, "`a.`.`[b*`.c2"},
{[]string{"`a", `b\`}, "`\\`a`.`b\\\\`"},
} {
got := test.in.toServiceFieldPath()
if got != test.want {
t.Errorf("%v: got %s, want %s", test.in, got, test.want)
}
}
}
func TestToServiceFieldPathComponent(t *testing.T) {
for _, test := range []struct {
in, want string
}{
{"", "``"},
{"clam_chowder23", "clam_chowder23"},
{"23skidoo", "`23skidoo`"},
{"bak`tik", "`bak\\`tik`"},
{"a\\b", "`a\\\\b`"},
{"dots.are.confusing", "`dots.are.confusing`"},
} {
got := toServiceFieldPathComponent(test.in)
if got != test.want {
t.Errorf("%q: got %q, want %q", test.in, got, test.want)
}
}
}

400
vendor/cloud.google.com/go/firestore/from_value.go generated vendored Normal file
View File

@@ -0,0 +1,400 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"reflect"
"strings"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
)
func setFromProtoValue(x interface{}, vproto *pb.Value, c *Client) error {
v := reflect.ValueOf(x)
if v.Kind() != reflect.Ptr || v.IsNil() {
return errors.New("firestore: nil or not a pointer")
}
return setReflectFromProtoValue(v.Elem(), vproto, c)
}
// setReflectFromProtoValue sets v from a Firestore Value.
// v must be a settable value.
func setReflectFromProtoValue(v reflect.Value, vproto *pb.Value, c *Client) error {
typeErr := func() error {
return fmt.Errorf("firestore: cannot set type %s to %s", v.Type(), typeString(vproto))
}
val := vproto.ValueType
// A Null value sets anything nullable to nil, and has no effect
// on anything else.
if _, ok := val.(*pb.Value_NullValue); ok {
switch v.Kind() {
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
v.Set(reflect.Zero(v.Type()))
}
return nil
}
// Handle special types first.
switch v.Type() {
case typeOfByteSlice:
x, ok := val.(*pb.Value_BytesValue)
if !ok {
return typeErr()
}
v.SetBytes(x.BytesValue)
return nil
case typeOfGoTime:
x, ok := val.(*pb.Value_TimestampValue)
if !ok {
return typeErr()
}
t, err := ptypes.Timestamp(x.TimestampValue)
if err != nil {
return err
}
v.Set(reflect.ValueOf(t))
return nil
case typeOfLatLng:
x, ok := val.(*pb.Value_GeoPointValue)
if !ok {
return typeErr()
}
v.Set(reflect.ValueOf(x.GeoPointValue))
return nil
case typeOfDocumentRef:
x, ok := val.(*pb.Value_ReferenceValue)
if !ok {
return typeErr()
}
dr, err := pathToDoc(x.ReferenceValue, c)
if err != nil {
return err
}
v.Set(reflect.ValueOf(dr))
return nil
}
switch v.Kind() {
case reflect.Bool:
x, ok := val.(*pb.Value_BooleanValue)
if !ok {
return typeErr()
}
v.SetBool(x.BooleanValue)
case reflect.String:
x, ok := val.(*pb.Value_StringValue)
if !ok {
return typeErr()
}
v.SetString(x.StringValue)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x, ok := val.(*pb.Value_IntegerValue)
if !ok {
return typeErr()
}
i := x.IntegerValue
if v.OverflowInt(i) {
return overflowErr(v, i)
}
v.SetInt(i)
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
x, ok := val.(*pb.Value_IntegerValue)
if !ok {
return typeErr()
}
u := uint64(x.IntegerValue)
if v.OverflowUint(u) {
return overflowErr(v, u)
}
v.SetUint(u)
case reflect.Float32, reflect.Float64:
x, ok := val.(*pb.Value_DoubleValue)
if !ok {
return typeErr()
}
f := x.DoubleValue
if v.OverflowFloat(f) {
return overflowErr(v, f)
}
v.SetFloat(f)
case reflect.Slice:
x, ok := val.(*pb.Value_ArrayValue)
if !ok {
return typeErr()
}
vals := x.ArrayValue.Values
vlen := v.Len()
xlen := len(vals)
// Make a slice of the right size, avoiding allocation if possible.
switch {
case vlen < xlen:
v.Set(reflect.MakeSlice(v.Type(), xlen, xlen))
case vlen > xlen:
v.SetLen(xlen)
}
return populateRepeated(v, vals, xlen, c)
case reflect.Array:
x, ok := val.(*pb.Value_ArrayValue)
if !ok {
return typeErr()
}
vals := x.ArrayValue.Values
xlen := len(vals)
vlen := v.Len()
minlen := vlen
// Set extra elements to their zero value.
if vlen > xlen {
z := reflect.Zero(v.Type().Elem())
for i := xlen; i < vlen; i++ {
v.Index(i).Set(z)
}
minlen = xlen
}
return populateRepeated(v, vals, minlen, c)
case reflect.Map:
x, ok := val.(*pb.Value_MapValue)
if !ok {
return typeErr()
}
return populateMap(v, x.MapValue.Fields, c)
case reflect.Ptr:
// If the pointer is nil, set it to a zero value.
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
return setReflectFromProtoValue(v.Elem(), vproto, c)
case reflect.Struct:
x, ok := val.(*pb.Value_MapValue)
if !ok {
return typeErr()
}
return populateStruct(v, x.MapValue.Fields, c)
case reflect.Interface:
if v.NumMethod() == 0 { // empty interface
// If v holds a pointer, set the pointer.
if !v.IsNil() && v.Elem().Kind() == reflect.Ptr {
return setReflectFromProtoValue(v.Elem(), vproto, c)
}
// Otherwise, create a fresh value.
x, err := createFromProtoValue(vproto, c)
if err != nil {
return err
}
v.Set(reflect.ValueOf(x))
return nil
}
// Any other kind of interface is an error.
fallthrough
default:
return fmt.Errorf("firestore: cannot set type %s", v.Type())
}
return nil
}
// populateRepeated sets the first n elements of vr, which must be a slice or
// array, to the corresponding elements of vals.
func populateRepeated(vr reflect.Value, vals []*pb.Value, n int, c *Client) error {
for i := 0; i < n; i++ {
if err := setReflectFromProtoValue(vr.Index(i), vals[i], c); err != nil {
return err
}
}
return nil
}
// populateMap sets the elements of vm, which must be a map, from the
// corresponding elements of pm.
//
// Since a map value is not settable, this function always creates a new
// element for each corresponding map key. Existing values of vm are
// overwritten. This happens even if the map value is something like a pointer
// to a struct, where we could in theory populate the existing struct value
// instead of discarding it. This behavior matches encoding/json.
func populateMap(vm reflect.Value, pm map[string]*pb.Value, c *Client) error {
t := vm.Type()
if t.Key().Kind() != reflect.String {
return errors.New("firestore: map key type is not string")
}
if vm.IsNil() {
vm.Set(reflect.MakeMap(t))
}
et := t.Elem()
for k, vproto := range pm {
el := reflect.New(et).Elem()
if err := setReflectFromProtoValue(el, vproto, c); err != nil {
return err
}
vm.SetMapIndex(reflect.ValueOf(k), el)
}
return nil
}
// createMapFromValueMap creates a fresh map and populates it with pm.
func createMapFromValueMap(pm map[string]*pb.Value, c *Client) (map[string]interface{}, error) {
m := map[string]interface{}{}
for k, pv := range pm {
v, err := createFromProtoValue(pv, c)
if err != nil {
return nil, err
}
m[k] = v
}
return m, nil
}
// populateStruct sets the fields of vs, which must be a struct, from
// the matching elements of pm.
func populateStruct(vs reflect.Value, pm map[string]*pb.Value, c *Client) error {
fields, err := fieldCache.Fields(vs.Type())
if err != nil {
return err
}
for k, vproto := range pm {
f := fields.Match(k)
if f == nil {
continue
}
if err := setReflectFromProtoValue(vs.FieldByIndex(f.Index), vproto, c); err != nil {
return fmt.Errorf("%s.%s: %v", vs.Type(), f.Name, err)
}
}
return nil
}
func createFromProtoValue(vproto *pb.Value, c *Client) (interface{}, error) {
switch v := vproto.ValueType.(type) {
case *pb.Value_NullValue:
return nil, nil
case *pb.Value_BooleanValue:
return v.BooleanValue, nil
case *pb.Value_IntegerValue:
return v.IntegerValue, nil
case *pb.Value_DoubleValue:
return v.DoubleValue, nil
case *pb.Value_TimestampValue:
return ptypes.Timestamp(v.TimestampValue)
case *pb.Value_StringValue:
return v.StringValue, nil
case *pb.Value_BytesValue:
return v.BytesValue, nil
case *pb.Value_ReferenceValue:
return pathToDoc(v.ReferenceValue, c)
case *pb.Value_GeoPointValue:
return v.GeoPointValue, nil
case *pb.Value_ArrayValue:
vals := v.ArrayValue.Values
ret := make([]interface{}, len(vals))
for i, v := range vals {
r, err := createFromProtoValue(v, c)
if err != nil {
return nil, err
}
ret[i] = r
}
return ret, nil
case *pb.Value_MapValue:
fields := v.MapValue.Fields
ret := make(map[string]interface{}, len(fields))
for k, v := range fields {
r, err := createFromProtoValue(v, c)
if err != nil {
return nil, err
}
ret[k] = r
}
return ret, nil
default:
return nil, fmt.Errorf("firestore: unknown value type %T", v)
}
}
// Convert a document path to a DocumentRef.
func pathToDoc(docPath string, c *Client) (*DocumentRef, error) {
projID, dbID, docIDs, err := parseDocumentPath(docPath)
if err != nil {
return nil, err
}
parentResourceName := fmt.Sprintf("projects/%s/databases/%s", projID, dbID)
_, doc := c.idsToRef(docIDs, parentResourceName)
return doc, nil
}
// A document path should be of the form "projects/P/databases/D/documents/coll1/doc1/coll2/doc2/...".
func parseDocumentPath(path string) (projectID, databaseID string, docPath []string, err error) {
parts := strings.Split(path, "/")
if len(parts) < 6 || parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" {
return "", "", nil, fmt.Errorf("firestore: malformed document path %q", path)
}
docp := parts[5:]
if len(docp)%2 != 0 {
return "", "", nil, fmt.Errorf("firestore: path %q refers to collection, not document", path)
}
return parts[1], parts[3], docp, nil
}
func typeString(vproto *pb.Value) string {
switch vproto.ValueType.(type) {
case *pb.Value_NullValue:
return "null"
case *pb.Value_BooleanValue:
return "bool"
case *pb.Value_IntegerValue:
return "int"
case *pb.Value_DoubleValue:
return "float"
case *pb.Value_TimestampValue:
return "timestamp"
case *pb.Value_StringValue:
return "string"
case *pb.Value_BytesValue:
return "bytes"
case *pb.Value_ReferenceValue:
return "reference"
case *pb.Value_GeoPointValue:
return "GeoPoint"
case *pb.Value_MapValue:
return "map"
case *pb.Value_ArrayValue:
return "array"
default:
return "<unknown Value type>"
}
}
func overflowErr(v reflect.Value, x interface{}) error {
return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type())
}

541
vendor/cloud.google.com/go/firestore/from_value_test.go generated vendored Normal file
View File

@@ -0,0 +1,541 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"encoding/json"
"fmt"
"io"
"math"
"reflect"
"strings"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/genproto/googleapis/type/latlng"
)
var (
tm = time.Date(2016, 12, 25, 0, 0, 0, 123456789, time.UTC)
ll = &latlng.LatLng{Latitude: 20, Longitude: 30}
)
func TestCreateFromProtoValue(t *testing.T) {
for _, test := range []struct {
in *pb.Value
want interface{}
}{
{in: nullValue, want: nil},
{in: boolval(true), want: true},
{in: intval(3), want: int64(3)},
{in: floatval(1.5), want: 1.5},
{in: strval("str"), want: "str"},
{in: tsval(tm), want: tm},
{
in: bytesval([]byte{1, 2}),
want: []byte{1, 2},
},
{
in: &pb.Value{&pb.Value_GeoPointValue{ll}},
want: ll,
},
{
in: arrayval(intval(1), intval(2)),
want: []interface{}{int64(1), int64(2)},
},
{
in: arrayval(),
want: []interface{}{},
},
{
in: mapval(map[string]*pb.Value{"a": intval(1), "b": intval(2)}),
want: map[string]interface{}{"a": int64(1), "b": int64(2)},
},
{
in: mapval(map[string]*pb.Value{}),
want: map[string]interface{}{},
},
{
in: refval("projects/P/databases/D/documents/c/d"),
want: &DocumentRef{
ID: "d",
Parent: &CollectionRef{
ID: "c",
parentPath: "projects/P/databases/D",
Path: "projects/P/databases/D/documents/c",
Query: Query{collectionID: "c", parentPath: "projects/P/databases/D"},
},
Path: "projects/P/databases/D/documents/c/d",
},
},
} {
got, err := createFromProtoValue(test.in, nil)
if err != nil {
t.Errorf("%v: %v", test.in, err)
continue
}
if !testEqual(got, test.want) {
t.Errorf("%+v:\ngot\n%#v\nwant\n%#v", test.in, got, test.want)
}
}
}
func TestSetFromProtoValue(t *testing.T) {
testSetFromProtoValue(t, "json", jsonTester{})
testSetFromProtoValue(t, "firestore", protoTester{})
}
func testSetFromProtoValue(t *testing.T, prefix string, r tester) {
pi := newfloat(7)
s := []float64{7, 8}
ar1 := [1]float64{7}
ar2 := [2]float64{7, 8}
ar3 := [3]float64{7, 8, 9}
mf := map[string]float64{"a": 7}
type T struct {
I **float64
J float64
}
one := newfloat(1)
six := newfloat(6)
st := []*T{&T{I: &six}, nil, &T{I: &six, J: 7}}
vs := interface{}(T{J: 1})
vm := interface{}(map[string]float64{"i": 1})
var (
i int
i8 int8
i16 int16
i32 int32
i64 int64
u8 uint8
u16 uint16
u32 uint32
b bool
ll *latlng.LatLng
mi map[string]interface{}
ms map[string]T
)
for i, test := range []struct {
in interface{}
val interface{}
want interface{}
}{
{&pi, r.Null(), (*float64)(nil)},
{pi, r.Float(1), 1.0},
{&s, r.Null(), ([]float64)(nil)},
{&s, r.Array(r.Float(1), r.Float(2)), []float64{1, 2}},
{&ar1, r.Array(r.Float(1), r.Float(2)), [1]float64{1}},
{&ar2, r.Array(r.Float(1), r.Float(2)), [2]float64{1, 2}},
{&ar3, r.Array(r.Float(1), r.Float(2)), [3]float64{1, 2, 0}},
{&mf, r.Null(), (map[string]float64)(nil)},
{&mf, r.Map("a", r.Float(1), "b", r.Float(2)), map[string]float64{"a": 1, "b": 2}},
{&st, r.Array(
r.Null(), // overwrites st[0] with nil
r.Map("i", r.Float(1)), // sets st[1] to a new struct
r.Map("i", r.Float(2)), // modifies st[2]
),
[]*T{nil, &T{I: &one}, &T{I: &six, J: 7}}},
{&mi, r.Map("a", r.Float(1), "b", r.Float(2)), map[string]interface{}{"a": 1.0, "b": 2.0}},
{&ms, r.Map("a", r.Map("j", r.Float(1))), map[string]T{"a": T{J: 1}}},
{&vs, r.Map("i", r.Float(2)), map[string]interface{}{"i": 2.0}},
{&vm, r.Map("i", r.Float(2)), map[string]interface{}{"i": 2.0}},
{&ll, r.Null(), (*latlng.LatLng)(nil)},
{&i, r.Int(1), int(1)},
{&i8, r.Int(1), int8(1)},
{&i16, r.Int(1), int16(1)},
{&i32, r.Int(1), int32(1)},
{&i64, r.Int(1), int64(1)},
{&u8, r.Int(1), uint8(1)},
{&u16, r.Int(1), uint16(1)},
{&u32, r.Int(1), uint32(1)},
{&b, r.Bool(true), true},
} {
if err := r.Set(test.in, test.val); err != nil {
t.Errorf("%s: #%d: got error %v", prefix, i, err)
continue
}
got := reflect.ValueOf(test.in).Elem().Interface()
if !testEqual(got, test.want) {
t.Errorf("%s: #%d, %v:\ngot\n%+v (%T)\nwant\n%+v (%T)",
prefix, i, test.val, got, got, test.want, test.want)
}
}
}
func TestSetFromProtoValueNoJSON(t *testing.T) {
// Test code paths that we cannot compare to JSON.
var (
bs []byte
tmi time.Time
lli *latlng.LatLng
)
bytes := []byte{1, 2, 3}
for i, test := range []struct {
in interface{}
val *pb.Value
want interface{}
}{
{&bs, bytesval(bytes), bytes},
{&tmi, tsval(tm), tm},
{&lli, geoval(ll), ll},
} {
if err := setFromProtoValue(test.in, test.val, &Client{}); err != nil {
t.Errorf("#%d: got error %v", i, err)
continue
}
got := reflect.ValueOf(test.in).Elem().Interface()
if !testEqual(got, test.want) {
t.Errorf("#%d, %v:\ngot\n%+v (%T)\nwant\n%+v (%T)",
i, test.val, got, got, test.want, test.want)
}
}
}
func TestSetFromProtoValueErrors(t *testing.T) {
c := &Client{}
ival := intval(3)
for _, test := range []struct {
in interface{}
val *pb.Value
}{
{3, ival}, // not a pointer
{new(int8), intval(128)}, // int overflow
{new(uint8), intval(256)}, // uint overflow
{new(float32), floatval(2 * math.MaxFloat32)}, // float overflow
{new(uint), ival}, // cannot set type
{new(uint64), ival}, // cannot set type
{new(io.Reader), ival}, // cannot set type
{new(map[int]int),
mapval(map[string]*pb.Value{"x": ival})}, // map key type is not string
// the rest are all type mismatches
{new(bool), ival},
{new(*latlng.LatLng), ival},
{new(time.Time), ival},
{new(string), ival},
{new(float64), ival},
{new([]byte), ival},
{new([]int), ival},
{new([1]int), ival},
{new(map[string]int), ival},
{new(*bool), ival},
{new(struct{}), ival},
{new(int), floatval(3)},
{new(uint16), floatval(3)},
} {
err := setFromProtoValue(test.in, test.val, c)
if err == nil {
t.Errorf("%v, %v: got nil, want error", test.in, test.val)
}
}
}
func TestSetFromProtoValuePointers(t *testing.T) {
// Verify that pointers are set, instead of being replaced.
// Confirm that the behavior matches encoding/json.
testSetPointer(t, "json", jsonTester{})
testSetPointer(t, "firestore", protoTester{&Client{}})
}
func testSetPointer(t *testing.T, prefix string, r tester) {
// If an interface{} holds a pointer, the pointer is set.
set := func(x, val interface{}) {
if err := r.Set(x, val); err != nil {
t.Fatalf("%s: set(%v, %v): %v", prefix, x, val, err)
}
}
p := new(float64)
var st struct {
I interface{}
}
// A pointer in a slice of interface{} is set.
s := []interface{}{p}
set(&s, r.Array(r.Float(1)))
if s[0] != p {
t.Errorf("%s: pointers not identical", prefix)
}
if *p != 1 {
t.Errorf("%s: got %f, want 1", prefix, *p)
}
// Setting a null will set the pointer to nil.
set(&s, r.Array(r.Null()))
if got := s[0]; got != nil {
t.Errorf("%s: got %v, want null", prefix, got)
}
// It doesn't matter how deep the pointers nest.
p = new(float64)
p2 := &p
p3 := &p2
s = []interface{}{p3}
set(&s, r.Array(r.Float(1)))
if s[0] != p3 {
t.Errorf("%s: pointers not identical", prefix)
}
if *p != 1 {
t.Errorf("%s: got %f, want 1", prefix, *p)
}
// A pointer in an interface{} field is set.
p = new(float64)
st.I = p
set(&st, r.Map("i", r.Float(1)))
if st.I != p {
t.Errorf("%s: pointers not identical", prefix)
}
if *p != 1 {
t.Errorf("%s: got %f, want 1", prefix, *p)
}
// Setting a null will set the pointer to nil.
set(&st, r.Map("i", r.Null()))
if got := st.I; got != nil {
t.Errorf("%s: got %v, want null", prefix, got)
}
// A pointer to a slice (instead of to float64) is set.
psi := &[]float64{7, 8, 9}
st.I = psi
set(&st, r.Map("i", r.Array(r.Float(1))))
if st.I != psi {
t.Errorf("%s: pointers not identical", prefix)
}
// The slice itself should be truncated and filled, not replaced.
if got, want := cap(*psi), 3; got != want {
t.Errorf("cap: got %d, want %d", got, want)
}
if want := &[]float64{1}; !testEqual(st.I, want) {
t.Errorf("got %+v, want %+v", st.I, want)
}
// A pointer to a map is set.
pmf := &map[string]float64{"a": 7, "b": 8}
st.I = pmf
set(&st, r.Map("i", r.Map("a", r.Float(1))))
if st.I != pmf {
t.Errorf("%s: pointers not identical", prefix)
}
if want := map[string]float64{"a": 1, "b": 8}; !testEqual(*pmf, want) {
t.Errorf("%s: got %+v, want %+v", prefix, *pmf, want)
}
// Maps are different: since the map values aren't addressable, they
// are always discarded, even if the map element type is not interface{}.
// A map's values are discarded if the value type is a pointer type.
p = new(float64)
m := map[string]*float64{"i": p}
set(&m, r.Map("i", r.Float(1)))
if m["i"] == p {
t.Errorf("%s: pointers are identical", prefix)
}
if got, want := *m["i"], 1.0; got != want {
t.Errorf("%s: got %v, want %v", prefix, got, want)
}
// A map's values are discarded if the value type is interface{}.
p = new(float64)
m2 := map[string]interface{}{"i": p}
set(&m2, r.Map("i", r.Float(1)))
if m2["i"] == p {
t.Errorf("%s: pointers are identical", prefix)
}
if got, want := m2["i"].(float64), 1.0; got != want {
t.Errorf("%s: got %f, want %f", prefix, got, want)
}
}
// An interface for setting and building values, to facilitate comparing firestore deserialization
// with encoding/json.
type tester interface {
Set(x, val interface{}) error
Null() interface{}
Int(int) interface{}
Float(float64) interface{}
Bool(bool) interface{}
Array(...interface{}) interface{}
Map(keysvals ...interface{}) interface{}
}
type protoTester struct {
c *Client
}
func (p protoTester) Set(x, val interface{}) error { return setFromProtoValue(x, val.(*pb.Value), p.c) }
func (protoTester) Null() interface{} { return nullValue }
func (protoTester) Int(i int) interface{} { return intval(i) }
func (protoTester) Float(f float64) interface{} { return floatval(f) }
func (protoTester) Bool(b bool) interface{} { return boolval(b) }
func (protoTester) Array(els ...interface{}) interface{} {
var s []*pb.Value
for _, el := range els {
s = append(s, el.(*pb.Value))
}
return arrayval(s...)
}
func (protoTester) Map(keysvals ...interface{}) interface{} {
m := map[string]*pb.Value{}
for i := 0; i < len(keysvals); i += 2 {
m[keysvals[i].(string)] = keysvals[i+1].(*pb.Value)
}
return mapval(m)
}
type jsonTester struct{}
func (jsonTester) Set(x, val interface{}) error { return json.Unmarshal([]byte(val.(string)), x) }
func (jsonTester) Null() interface{} { return "null" }
func (jsonTester) Int(i int) interface{} { return fmt.Sprint(i) }
func (jsonTester) Float(f float64) interface{} { return fmt.Sprint(f) }
func (jsonTester) Bool(b bool) interface{} {
if b {
return "true"
}
return "false"
}
func (jsonTester) Array(els ...interface{}) interface{} {
var s []string
for _, el := range els {
s = append(s, el.(string))
}
return "[" + strings.Join(s, ", ") + "]"
}
func (jsonTester) Map(keysvals ...interface{}) interface{} {
var s []string
for i := 0; i < len(keysvals); i += 2 {
s = append(s, fmt.Sprintf("%q: %v", keysvals[i], keysvals[i+1]))
}
return "{" + strings.Join(s, ", ") + "}"
}
func newfloat(f float64) *float64 {
p := new(float64)
*p = f
return p
}
func TestParseDocumentPath(t *testing.T) {
for _, test := range []struct {
in string
pid, dbid string
dpath []string
}{
{"projects/foo-bar/databases/db2/documents/c1/d1",
"foo-bar", "db2", []string{"c1", "d1"}},
{"projects/P/databases/D/documents/c1/d1/c2/d2",
"P", "D", []string{"c1", "d1", "c2", "d2"}},
} {
gotPid, gotDbid, gotDpath, err := parseDocumentPath(test.in)
if err != nil {
t.Fatal(err)
}
if got, want := gotPid, test.pid; got != want {
t.Errorf("project ID: got %q, want %q", got, want)
}
if got, want := gotDbid, test.dbid; got != want {
t.Errorf("db ID: got %q, want %q", got, want)
}
if got, want := gotDpath, test.dpath; !testEqual(got, want) {
t.Errorf("doc path: got %q, want %q", got, want)
}
}
}
func TestParseDocumentPathErrors(t *testing.T) {
for _, badPath := range []string{
"projects/P/databases/D/documents/c", // collection path
"/projects/P/databases/D/documents/c/d", // initial slash
"projects/P/databases/D/c/d", // missing "documents"
"project/P/database/D/document/c/d",
} {
// Every prefix of a bad path is also bad.
for i := 0; i <= len(badPath); i++ {
in := badPath[:i]
_, _, _, err := parseDocumentPath(in)
if err == nil {
t.Errorf("%q: got nil, want error", in)
}
}
}
}
func TestPathToDoc(t *testing.T) {
c := &Client{}
path := "projects/P/databases/D/documents/c1/d1/c2/d2"
got, err := pathToDoc(path, c)
if err != nil {
t.Fatal(err)
}
want := &DocumentRef{
ID: "d2",
Path: "projects/P/databases/D/documents/c1/d1/c2/d2",
Parent: &CollectionRef{
ID: "c2",
parentPath: "projects/P/databases/D/documents/c1/d1",
Path: "projects/P/databases/D/documents/c1/d1/c2",
c: c,
Query: Query{c: c, collectionID: "c2", parentPath: "projects/P/databases/D/documents/c1/d1"},
Parent: &DocumentRef{
ID: "d1",
Path: "projects/P/databases/D/documents/c1/d1",
Parent: &CollectionRef{
ID: "c1",
c: c,
parentPath: "projects/P/databases/D",
Path: "projects/P/databases/D/documents/c1",
Parent: nil,
Query: Query{c: c, collectionID: "c1", parentPath: "projects/P/databases/D"},
},
},
},
}
if !testEqual(got, want) {
t.Errorf("\ngot %+v\nwant %+v", got, want)
}
}
func TestTypeString(t *testing.T) {
for _, test := range []struct {
in *pb.Value
want string
}{
{nullValue, "null"},
{intval(1), "int"},
{floatval(1), "float"},
{boolval(true), "bool"},
{strval(""), "string"},
{tsval(tm), "timestamp"},
{geoval(ll), "GeoPoint"},
{bytesval(nil), "bytes"},
{refval(""), "reference"},
{arrayval(nil), "array"},
{mapval(nil), "map"},
} {
got := typeString(test.in)
if got != test.want {
t.Errorf("%+v: got %q, want %q", test.in, got, test.want)
}
}
}

View File

@@ -0,0 +1,954 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"flag"
"fmt"
"log"
"os"
"sort"
"testing"
"time"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/genproto/googleapis/type/latlng"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
func TestMain(m *testing.M) {
initIntegrationTest()
status := m.Run()
cleanupIntegrationTest()
os.Exit(status)
}
const (
envProjID = "GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID"
envPrivateKey = "GCLOUD_TESTS_GOLANG_FIRESTORE_KEY"
)
var (
iClient *Client
iColl *CollectionRef
collectionIDs = testutil.NewUIDSpace("go-integration-test")
)
func initIntegrationTest() {
flag.Parse() // needed for testing.Short()
if testing.Short() {
return
}
ctx := context.Background()
testProjectID := os.Getenv(envProjID)
if testProjectID == "" {
log.Println("Integration tests skipped. See CONTRIBUTING.md for details")
return
}
ts := testutil.TokenSourceEnv(ctx, envPrivateKey,
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/datastore")
if ts == nil {
log.Fatal("The project key must be set. See CONTRIBUTING.md for details")
}
ti := &testInterceptor{dbPath: "projects/" + testProjectID + "/databases/(default)"}
c, err := NewClient(ctx, testProjectID,
option.WithTokenSource(ts),
option.WithGRPCDialOption(grpc.WithUnaryInterceptor(ti.interceptUnary)),
option.WithGRPCDialOption(grpc.WithStreamInterceptor(ti.interceptStream)),
)
if err != nil {
log.Fatalf("NewClient: %v", err)
}
iClient = c
iColl = c.Collection(collectionIDs.New())
refDoc := iColl.NewDoc()
integrationTestMap["ref"] = refDoc
wantIntegrationTestMap["ref"] = refDoc
integrationTestStruct.Ref = refDoc
}
type testInterceptor struct {
dbPath string
}
func (ti *testInterceptor) interceptUnary(ctx context.Context, method string, req, res interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ti.checkMetadata(ctx, method)
return invoker(ctx, method, req, res, cc, opts...)
}
func (ti *testInterceptor) interceptStream(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ti.checkMetadata(ctx, method)
return streamer(ctx, desc, cc, method, opts...)
}
func (ti *testInterceptor) checkMetadata(ctx context.Context, method string) {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
log.Fatalf("method %s: bad metadata", method)
}
for _, h := range []string{"google-cloud-resource-prefix", "x-goog-api-client"} {
v, ok := md[h]
if !ok {
log.Fatalf("method %s, header %s missing", method, h)
}
if len(v) != 1 {
log.Fatalf("method %s, header %s: bad value %v", method, h, v)
}
}
v := md["google-cloud-resource-prefix"][0]
if v != ti.dbPath {
log.Fatalf("method %s: bad resource prefix header: %q", method, v)
}
}
func cleanupIntegrationTest() {
if iClient == nil {
return
}
// TODO(jba): delete everything in integrationColl.
iClient.Close()
}
// integrationClient should be called by integration tests to get a valid client. It will never
// return nil. If integrationClient returns, an integration test can proceed without
// further checks.
func integrationClient(t *testing.T) *Client {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
if iClient == nil {
t.SkipNow() // log message printed in initIntegrationTest
}
return iClient
}
func integrationColl(t *testing.T) *CollectionRef {
_ = integrationClient(t)
return iColl
}
type integrationTestStructType struct {
Int int
Str string
Bool bool
Float float32
Null interface{}
Bytes []byte
Time time.Time
Geo, NilGeo *latlng.LatLng
Ref *DocumentRef
}
var (
integrationTime = time.Date(2017, 3, 20, 1, 2, 3, 456789, time.UTC)
// Firestore times are accurate only to microseconds.
wantIntegrationTime = time.Date(2017, 3, 20, 1, 2, 3, 456000, time.UTC)
integrationGeo = &latlng.LatLng{Latitude: 30, Longitude: 70}
// Use this when writing a doc.
integrationTestMap = map[string]interface{}{
"int": 1,
"str": "two",
"bool": true,
"float": 3.14,
"null": nil,
"bytes": []byte("bytes"),
"*": map[string]interface{}{"`": 4},
"time": integrationTime,
"geo": integrationGeo,
"ref": nil, // populated by initIntegrationTest
}
// The returned data is slightly different.
wantIntegrationTestMap = map[string]interface{}{
"int": int64(1),
"str": "two",
"bool": true,
"float": 3.14,
"null": nil,
"bytes": []byte("bytes"),
"*": map[string]interface{}{"`": int64(4)},
"time": wantIntegrationTime,
"geo": integrationGeo,
"ref": nil, // populated by initIntegrationTest
}
integrationTestStruct = integrationTestStructType{
Int: 1,
Str: "two",
Bool: true,
Float: 3.14,
Null: nil,
Bytes: []byte("bytes"),
Time: integrationTime,
Geo: integrationGeo,
NilGeo: nil,
Ref: nil, // populated by initIntegrationTest
}
)
func TestIntegration_Create(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
start := time.Now()
wr := mustCreate("Create #1", t, doc, integrationTestMap)
end := time.Now()
checkTimeBetween(t, wr.UpdateTime, start, end)
_, err := doc.Create(ctx, integrationTestMap)
codeEq(t, "Create on a present doc", codes.AlreadyExists, err)
}
func TestIntegration_Get(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
mustCreate("Get #1", t, doc, integrationTestMap)
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
if ds.CreateTime != ds.UpdateTime {
t.Errorf("create time %s != update time %s", ds.CreateTime, ds.UpdateTime)
}
got := ds.Data()
if want := wantIntegrationTestMap; !testEqual(got, want) {
t.Errorf("got\n%v\nwant\n%v", pretty.Value(got), pretty.Value(want))
}
//
_, err = integrationColl(t).NewDoc().Get(ctx)
codeEq(t, "Get on a missing doc", codes.NotFound, err)
}
func TestIntegration_GetAll(t *testing.T) {
type getAll struct{ N int }
coll := integrationColl(t)
ctx := context.Background()
var docRefs []*DocumentRef
for i := 0; i < 5; i++ {
doc := coll.NewDoc()
docRefs = append(docRefs, doc)
mustCreate("GetAll #1", t, doc, getAll{N: i})
}
docSnapshots, err := iClient.GetAll(ctx, docRefs)
if err != nil {
t.Fatal(err)
}
if got, want := len(docSnapshots), len(docRefs); got != want {
t.Fatalf("got %d snapshots, want %d", got, want)
}
for i, ds := range docSnapshots {
var got getAll
if err := ds.DataTo(&got); err != nil {
t.Fatal(err)
}
want := getAll{N: i}
if got != want {
t.Errorf("%d: got %+v, want %+v", i, got, want)
}
}
}
func TestIntegration_Add(t *testing.T) {
start := time.Now()
_, wr, err := integrationColl(t).Add(context.Background(), integrationTestMap)
if err != nil {
t.Fatal(err)
}
end := time.Now()
checkTimeBetween(t, wr.UpdateTime, start, end)
}
func TestIntegration_Set(t *testing.T) {
coll := integrationColl(t)
ctx := context.Background()
// Set Should be able to create a new doc.
doc := coll.NewDoc()
wr1, err := doc.Set(ctx, integrationTestMap)
if err != nil {
t.Fatal(err)
}
// Calling Set on the doc completely replaces the contents.
// The update time should increase.
newData := map[string]interface{}{
"str": "change",
"x": "1",
}
wr2, err := doc.Set(ctx, newData)
if err != nil {
t.Fatal(err)
}
if !wr1.UpdateTime.Before(wr2.UpdateTime) {
t.Errorf("update time did not increase: old=%s, new=%s", wr1.UpdateTime, wr2.UpdateTime)
}
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
got := ds.Data()
if !testEqual(got, newData) {
t.Errorf("got %v, want %v", got, newData)
}
newData = map[string]interface{}{
"str": "1",
"x": "2",
"y": "3",
}
// SetOptions:
// Only fields mentioned in the Merge option will be changed.
// In this case, "str" will not be changed to "1".
wr3, err := doc.Set(ctx, newData, Merge("x", "y"))
if err != nil {
t.Fatal(err)
}
ds, err = doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
got = ds.Data()
want := map[string]interface{}{
"str": "change",
"x": "2",
"y": "3",
}
if !testEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if !wr2.UpdateTime.Before(wr3.UpdateTime) {
t.Errorf("update time did not increase: old=%s, new=%s", wr2.UpdateTime, wr3.UpdateTime)
}
// Another way to change only x and y is to pass a map with only
// those keys, and use MergeAll.
wr4, err := doc.Set(ctx, map[string]interface{}{"x": "4", "y": "5"}, MergeAll)
if err != nil {
t.Fatal(err)
}
ds, err = doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
got = ds.Data()
want = map[string]interface{}{
"str": "change",
"x": "4",
"y": "5",
}
if !testEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if !wr3.UpdateTime.Before(wr4.UpdateTime) {
t.Errorf("update time did not increase: old=%s, new=%s", wr3.UpdateTime, wr4.UpdateTime)
}
}
func TestIntegration_Delete(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
mustCreate("Delete #1", t, doc, integrationTestMap)
wr, err := doc.Delete(ctx)
if err != nil {
t.Fatal(err)
}
// Confirm that doc doesn't exist.
if _, err := doc.Get(ctx); grpc.Code(err) != codes.NotFound {
t.Fatalf("got error <%v>, want NotFound", err)
}
er := func(_ *WriteResult, err error) error { return err }
codeEq(t, "Delete on a missing doc", codes.OK,
er(doc.Delete(ctx)))
// TODO(jba): confirm that the server should return InvalidArgument instead of
// FailedPrecondition.
wr = mustCreate("Delete #2", t, doc, integrationTestMap)
codeEq(t, "Delete with wrong LastUpdateTime", codes.FailedPrecondition,
er(doc.Delete(ctx, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond)))))
codeEq(t, "Delete with right LastUpdateTime", codes.OK,
er(doc.Delete(ctx, LastUpdateTime(wr.UpdateTime))))
}
func TestIntegration_UpdateMap(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
mustCreate("UpdateMap", t, doc, integrationTestMap)
um := map[string]interface{}{
"bool": false,
"time": 17,
"null": Delete,
"noSuchField": Delete, // deleting a non-existent field is a no-op
}
wr, err := doc.UpdateMap(ctx, um)
if err != nil {
t.Fatal(err)
}
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
got := ds.Data()
want := copyMap(wantIntegrationTestMap)
want["bool"] = false
want["time"] = int64(17)
delete(want, "null")
if !testEqual(got, want) {
t.Errorf("got\n%#v\nwant\n%#v", got, want)
}
er := func(_ *WriteResult, err error) error { return err }
codeEq(t, "UpdateMap on missing doc", codes.NotFound,
er(integrationColl(t).NewDoc().UpdateMap(ctx, um)))
codeEq(t, "UpdateMap with wrong LastUpdateTime", codes.FailedPrecondition,
er(doc.UpdateMap(ctx, um, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond)))))
codeEq(t, "UpdateMap with right LastUpdateTime", codes.OK,
er(doc.UpdateMap(ctx, um, LastUpdateTime(wr.UpdateTime))))
}
func TestIntegration_UpdateStruct(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
mustCreate("UpdateStruct", t, doc, integrationTestStruct)
fields := []string{"Bool", "Time", "Null", "noSuchField"}
wr, err := doc.UpdateStruct(ctx, fields,
integrationTestStructType{
Bool: false,
Time: aTime2,
})
if err != nil {
t.Fatal(err)
}
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
var got integrationTestStructType
if err := ds.DataTo(&got); err != nil {
t.Fatal(err)
}
want := integrationTestStruct
want.Bool = false
want.Time = aTime2
if !testEqual(got, want) {
t.Errorf("got\n%#v\nwant\n%#v", got, want)
}
er := func(_ *WriteResult, err error) error { return err }
codeEq(t, "UpdateStruct on missing doc", codes.NotFound,
er(integrationColl(t).NewDoc().UpdateStruct(ctx, fields, integrationTestStruct)))
codeEq(t, "UpdateStruct with wrong LastUpdateTime", codes.FailedPrecondition,
er(doc.UpdateStruct(ctx, fields, integrationTestStruct, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond)))))
codeEq(t, "UpdateStruct with right LastUpdateTime", codes.OK,
er(doc.UpdateStruct(ctx, fields, integrationTestStruct, LastUpdateTime(wr.UpdateTime))))
}
func TestIntegration_UpdatePaths(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
mustCreate("UpdatePaths", t, doc, integrationTestMap)
fpus := []FieldPathUpdate{
{Path: []string{"bool"}, Value: false},
{Path: []string{"time"}, Value: 17},
{Path: []string{"*", "`"}, Value: 18},
{Path: []string{"null"}, Value: Delete},
{Path: []string{"noSuchField"}, Value: Delete}, // deleting a non-existent field is a no-op
}
wr, err := doc.UpdatePaths(ctx, fpus)
if err != nil {
t.Fatal(err)
}
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
got := ds.Data()
want := copyMap(wantIntegrationTestMap)
want["bool"] = false
want["time"] = int64(17)
want["*"] = map[string]interface{}{"`": int64(18)}
delete(want, "null")
if !testEqual(got, want) {
t.Errorf("got\n%#v\nwant\n%#v", got, want)
}
er := func(_ *WriteResult, err error) error { return err }
codeEq(t, "UpdatePaths on missing doc", codes.NotFound,
er(integrationColl(t).NewDoc().UpdatePaths(ctx, fpus)))
codeEq(t, "UpdatePaths with wrong LastUpdateTime", codes.FailedPrecondition,
er(doc.UpdatePaths(ctx, fpus, LastUpdateTime(wr.UpdateTime.Add(-time.Millisecond)))))
codeEq(t, "UpdatePaths with right LastUpdateTime", codes.OK,
er(doc.UpdatePaths(ctx, fpus, LastUpdateTime(wr.UpdateTime))))
}
func TestIntegration_Collections(t *testing.T) {
ctx := context.Background()
c := integrationClient(t)
got, err := c.Collections(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
// There should be at least one collection.
if len(got) == 0 {
t.Error("got 0 top-level collections, want at least one")
}
doc := integrationColl(t).NewDoc()
got, err = doc.Collections(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("got %d collections, want 0", len(got))
}
var want []*CollectionRef
for i := 0; i < 3; i++ {
id := collectionIDs.New()
cr := doc.Collection(id)
want = append(want, cr)
mustCreate("Collections", t, cr.NewDoc(), integrationTestMap)
}
got, err = doc.Collections(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if !testEqual(got, want) {
t.Errorf("got\n%#v\nwant\n%#v", got, want)
}
}
func TestIntegration_ServerTimestamp(t *testing.T) {
type S struct {
A int
B time.Time
C time.Time `firestore:"C.C,serverTimestamp"`
D map[string]interface{}
E time.Time `firestore:",omitempty,serverTimestamp"`
}
data := S{
A: 1,
B: aTime,
// C is unset, so will get the server timestamp.
D: map[string]interface{}{"x": ServerTimestamp},
// E is unset, so will get the server timestamp.
}
ctx := context.Background()
doc := integrationColl(t).NewDoc()
// Bound times of the RPC, with some slack for clock skew.
start := time.Now()
mustCreate("ServerTimestamp", t, doc, data)
end := time.Now()
ds, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
var got S
if err := ds.DataTo(&got); err != nil {
t.Fatal(err)
}
if !testEqual(got.B, aTime) {
t.Errorf("B: got %s, want %s", got.B, aTime)
}
checkTimeBetween(t, got.C, start, end)
if g, w := got.D["x"], got.C; !testEqual(g, w) {
t.Errorf(`D["x"] = %s, want equal to C (%s)`, g, w)
}
if g, w := got.E, got.C; !testEqual(g, w) {
t.Errorf(`E = %s, want equal to C (%s)`, g, w)
}
}
func TestIntegration_MergeServerTimestamp(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
// Create a doc with an ordinary field "a" and a ServerTimestamp field "b".
_, err := doc.Set(ctx, map[string]interface{}{
"a": 1,
"b": ServerTimestamp})
if err != nil {
t.Fatal(err)
}
docSnap, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
data1 := docSnap.Data()
// Merge with a document with a different value of "a". However,
// specify only "b" in the list of merge fields.
_, err = doc.Set(ctx,
map[string]interface{}{"a": 2, "b": ServerTimestamp},
Merge("b"))
if err != nil {
t.Fatal(err)
}
// The result should leave "a" unchanged, while "b" is updated.
docSnap, err = doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
data2 := docSnap.Data()
if got, want := data2["a"], data1["a"]; got != want {
t.Errorf("got %v, want %v", got, want)
}
t1 := data1["b"].(time.Time)
t2 := data2["b"].(time.Time)
if !t1.Before(t2) {
t.Errorf("got t1=%s, t2=%s; want t1 before t2", t1, t2)
}
}
func TestIntegration_MergeNestedServerTimestamp(t *testing.T) {
ctx := context.Background()
doc := integrationColl(t).NewDoc()
// Create a doc with an ordinary field "a" a ServerTimestamp field "b",
// and a second ServerTimestamp field "c.d".
_, err := doc.Set(ctx, map[string]interface{}{
"a": 1,
"b": ServerTimestamp,
"c": map[string]interface{}{"d": ServerTimestamp},
})
if err != nil {
t.Fatal(err)
}
docSnap, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
data1 := docSnap.Data()
// Merge with a document with a different value of "a". However,
// specify only "c.d" in the list of merge fields.
_, err = doc.Set(ctx,
map[string]interface{}{
"a": 2,
"b": ServerTimestamp,
"c": map[string]interface{}{"d": ServerTimestamp},
},
Merge("c.d"))
if err != nil {
t.Fatal(err)
}
// The result should leave "a" and "b" unchanged, while "c.d" is updated.
docSnap, err = doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
data2 := docSnap.Data()
if got, want := data2["a"], data1["a"]; got != want {
t.Errorf("a: got %v, want %v", got, want)
}
want := data1["b"].(time.Time)
got := data2["b"].(time.Time)
if !got.Equal(want) {
t.Errorf("b: got %s, want %s", got, want)
}
t1 := data1["c"].(map[string]interface{})["d"].(time.Time)
t2 := data2["c"].(map[string]interface{})["d"].(time.Time)
if !t1.Before(t2) {
t.Errorf("got t1=%s, t2=%s; want t1 before t2", t1, t2)
}
}
func TestIntegration_WriteBatch(t *testing.T) {
ctx := context.Background()
b := integrationClient(t).Batch()
doc1 := iColl.NewDoc()
doc2 := iColl.NewDoc()
b.Create(doc1, integrationTestMap)
b.Set(doc2, integrationTestMap)
b.UpdateMap(doc1, map[string]interface{}{"bool": false})
b.UpdateMap(doc1, map[string]interface{}{"str": Delete})
wrs, err := b.Commit(ctx)
if err != nil {
t.Fatal(err)
}
if got, want := len(wrs), 4; got != want {
t.Fatalf("got %d WriteResults, want %d", got, want)
}
ds, err := doc1.Get(ctx)
if err != nil {
t.Fatal(err)
}
got1 := ds.Data()
want := copyMap(wantIntegrationTestMap)
want["bool"] = false
delete(want, "str")
if !testEqual(got1, want) {
t.Errorf("got\n%#v\nwant\n%#v", got1, want)
}
ds, err = doc2.Get(ctx)
if err != nil {
t.Fatal(err)
}
got2 := ds.Data()
if !testEqual(got2, wantIntegrationTestMap) {
t.Errorf("got\n%#v\nwant\n%#v", got2, wantIntegrationTestMap)
}
// TODO(jba): test two updates to the same document when it is supported.
// TODO(jba): test verify when it is supported.
}
func TestIntegration_Query(t *testing.T) {
ctx := context.Background()
coll := integrationColl(t)
var docs []*DocumentRef
var wants []map[string]interface{}
for i := 0; i < 3; i++ {
doc := coll.NewDoc()
docs = append(docs, doc)
// To support running this test in parallel with the others, use a field name
// that we don't use anywhere else.
mustCreate(fmt.Sprintf("Query #%d", i), t, doc,
map[string]interface{}{
"q": i,
"x": 1,
})
wants = append(wants, map[string]interface{}{"q": int64(i)})
}
q := coll.Select("q").OrderBy("q", Asc)
for i, test := range []struct {
q Query
want []map[string]interface{}
}{
{q, wants},
{q.Where("q", ">", 1), wants[2:]},
{q.WherePath([]string{"q"}, ">", 1), wants[2:]},
{q.Offset(1).Limit(1), wants[1:2]},
{q.StartAt(1), wants[1:]},
{q.StartAfter(1), wants[2:]},
{q.EndAt(1), wants[:2]},
{q.EndBefore(1), wants[:1]},
} {
gotDocs, err := test.q.Documents(ctx).GetAll()
if err != nil {
t.Errorf("#%d: %+v: %v", i, test.q, err)
continue
}
if len(gotDocs) != len(test.want) {
t.Errorf("#%d: %+v: got %d docs, want %d", i, test.q, len(gotDocs), len(test.want))
continue
}
for j, g := range gotDocs {
if got, want := g.Data(), test.want[j]; !testEqual(got, want) {
t.Errorf("#%d: %+v, #%d: got\n%+v\nwant\n%+v", i, test.q, j, got, want)
}
}
}
_, err := coll.Select("q").Where("x", "==", 1).OrderBy("q", Asc).Documents(ctx).GetAll()
codeEq(t, "Where and OrderBy on different fields without an index", codes.FailedPrecondition, err)
// Using the collection itself as the query should return the full documents.
allDocs, err := coll.Documents(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
seen := map[int64]bool{} // "q" values we see
for _, d := range allDocs {
data := d.Data()
q, ok := data["q"]
if !ok {
// A document from another test.
continue
}
if seen[q.(int64)] {
t.Errorf("%v: duplicate doc", data)
}
seen[q.(int64)] = true
if data["x"] != int64(1) {
t.Errorf("%v: wrong or missing 'x'", data)
}
if len(data) != 2 {
t.Errorf("%v: want two keys", data)
}
}
if got, want := len(seen), len(wants); got != want {
t.Errorf("got %d docs with 'q', want %d", len(seen), len(wants))
}
}
// Test the special DocumentID field in queries.
func TestIntegration_QueryName(t *testing.T) {
ctx := context.Background()
checkIDs := func(q Query, wantIDs []string) {
gots, err := q.Documents(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if len(gots) != len(wantIDs) {
t.Fatalf("got %d, want %d", len(gots), len(wantIDs))
}
for i, g := range gots {
if got, want := g.Ref.ID, wantIDs[i]; got != want {
t.Errorf("#%d: got %s, want %s", i, got, want)
}
}
}
coll := integrationColl(t)
var wantIDs []string
for i := 0; i < 3; i++ {
doc := coll.NewDoc()
mustCreate(fmt.Sprintf("Query #%d", i), t, doc, map[string]interface{}{"nm": 1})
wantIDs = append(wantIDs, doc.ID)
}
sort.Strings(wantIDs)
q := coll.Where("nm", "==", 1).OrderBy(DocumentID, Asc)
checkIDs(q, wantIDs)
// Empty Select.
q = coll.Select().Where("nm", "==", 1).OrderBy(DocumentID, Asc)
checkIDs(q, wantIDs)
// Test cursors with __name__.
checkIDs(q.StartAt(wantIDs[1]), wantIDs[1:])
checkIDs(q.EndAt(wantIDs[1]), wantIDs[:2])
}
func TestIntegration_QueryNested(t *testing.T) {
ctx := context.Background()
coll1 := integrationColl(t)
doc1 := coll1.NewDoc()
coll2 := doc1.Collection(collectionIDs.New())
doc2 := coll2.NewDoc()
wantData := map[string]interface{}{"x": int64(1)}
mustCreate("QueryNested", t, doc2, wantData)
q := coll2.Select("x")
got, err := q.Documents(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if len(got) != 1 {
t.Fatalf("got %d docs, want 1", len(got))
}
if gotData := got[0].Data(); !testEqual(gotData, wantData) {
t.Errorf("got\n%+v\nwant\n%+v", gotData, wantData)
}
}
func TestIntegration_RunTransaction(t *testing.T) {
ctx := context.Background()
type Player struct {
Name string
Score int
Star bool `firestore:"*"`
}
pat := Player{Name: "Pat", Score: 3, Star: false}
client := integrationClient(t)
patDoc := iColl.Doc("pat")
var anError error
incPat := func(_ context.Context, tx *Transaction) error {
doc, err := tx.Get(patDoc)
if err != nil {
return err
}
score, err := doc.DataAt("Score")
if err != nil {
return err
}
// Since the Star field is called "*", we must use DataAtPath to get it.
star, err := doc.DataAtPath([]string{"*"})
if err != nil {
return err
}
err = tx.UpdateStruct(patDoc, []string{"Score"},
Player{Score: int(score.(int64) + 7)})
if err != nil {
return err
}
// Since the Star field is called "*", we must use UpdatePaths to change it.
err = tx.UpdatePaths(patDoc,
[]FieldPathUpdate{{Path: []string{"*"}, Value: !star.(bool)}})
if err != nil {
return err
}
return anError
}
mustCreate("RunTransaction", t, patDoc, pat)
err := client.RunTransaction(ctx, incPat)
if err != nil {
t.Fatal(err)
}
ds, err := patDoc.Get(ctx)
if err != nil {
t.Fatal(err)
}
var got Player
if err := ds.DataTo(&got); err != nil {
t.Fatal(err)
}
want := Player{Name: "Pat", Score: 10, Star: true}
if got != want {
t.Errorf("got %+v, want %+v", got, want)
}
// Function returns error, so transaction is rolled back and no writes happen.
anError = errors.New("bad")
err = client.RunTransaction(ctx, incPat)
if err != anError {
t.Fatalf("got %v, want %v", err, anError)
}
if err := ds.DataTo(&got); err != nil {
t.Fatal(err)
}
// want is same as before.
if got != want {
t.Errorf("got %+v, want %+v", got, want)
}
}
func codeEq(t *testing.T, msg string, code codes.Code, err error) {
if grpc.Code(err) != code {
t.Fatalf("%s:\ngot <%v>\nwant code %s", msg, err, code)
}
}
func mustCreate(msg string, t *testing.T, doc *DocumentRef, data interface{}) *WriteResult {
wr, err := doc.Create(context.Background(), data)
if err != nil {
t.Fatalf("%s: creating: %v", msg, err)
}
return wr
}
func copyMap(m map[string]interface{}) map[string]interface{} {
c := map[string]interface{}{}
for k, v := range m {
c[k] = v
}
return c
}
func checkTimeBetween(t *testing.T, got, low, high time.Time) {
// Allow slack for clock skew.
const slack = 1 * time.Second
low = low.Add(-slack)
high = high.Add(slack)
if got.Before(low) || got.After(high) {
t.Fatalf("got %s, not in [%s, %s]", got, low, high)
}
}

10
vendor/cloud.google.com/go/firestore/internal/Makefile generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Build doc.go from template and snippets.
SHELL=/bin/bash
../doc.go: doc-snippets.go doc.template snipdoc.awk
@tmp=$$(mktemp) && \
awk -f snipdoc.awk doc-snippets.go doc.template > $$tmp && \
chmod +w ../doc.go && \
mv $$tmp ../doc.go && \
chmod -w ../doc.go

View File

@@ -0,0 +1,164 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"fmt"
firestore "cloud.google.com/go/firestore"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
)
const ELLIPSIS = 0
//[ structDef
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
//]
func f1() {
//[ NewClient
ctx := context.Background()
client, err := firestore.NewClient(ctx, "projectID")
if err != nil {
// TODO: Handle error.
}
//]
//[ refs
states := client.Collection("States")
ny := states.Doc("NewYork")
// Or, in a single call:
ny = client.Doc("States/NewYork")
//]
//[ docref.Get
docsnap, err := ny.Get(ctx)
if err != nil {
// TODO: Handle error.
}
dataMap := docsnap.Data()
fmt.Println(dataMap)
//]
//[ DataTo
var nyData State
if err := docsnap.DataTo(&nyData); err != nil {
// TODO: Handle error.
}
//]
//[ GetAll
docsnaps, err := client.GetAll(ctx, []*firestore.DocumentRef{
states.Doc("Wisconsin"), states.Doc("Ohio"),
})
if err != nil {
// TODO: Handle error.
}
for _, ds := range docsnaps {
_ = ds // TODO: Use ds.
}
//[ docref.Create
wr, err := ny.Create(ctx, State{
Capital: "Albany",
Population: 19.8,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr)
//]
//[ docref.Set
ca := states.Doc("California")
_, err = ca.Set(ctx, State{
Capital: "Sacramento",
Population: 39.14,
})
//]
//[ docref.Delete
_, err = ny.Delete(ctx)
//]
//[ LUT-precond
docsnap, err = ca.Get(ctx)
if err != nil {
// TODO: Handle error.
}
_, err = ca.UpdateStruct(ctx, []string{"capital"}, State{Capital: "Sacramento"},
firestore.LastUpdateTime(docsnap.UpdateTime))
//]
//[ docref.UpdateMap
_, err = ca.UpdateMap(ctx, map[string]interface{}{"pop": 39.2})
//]
//[ docref.UpdateStruct
_, err = ca.UpdateStruct(ctx, []string{"pop"}, State{Population: 39.2})
//]
//[ WriteBatch
writeResults, err := client.Batch().
Create(ny, State{Capital: "Albany"}).
UpdateStruct(ca, []string{"capital"}, State{Capital: "Sacramento"}).
Delete(client.Doc("States/WestDakota")).
Commit(ctx)
//]
_ = writeResults
//[ Query
q := states.Where("pop", ">", 10).OrderBy("pop", firestore.Desc)
//]
//[ Documents
iter := q.Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(doc.Data())
}
//]
//[ CollQuery
iter = client.Collection("States").Documents(ctx)
//]
}
func txn() {
var ctx context.Context
var client *firestore.Client
//[ Transaction
ny := client.Doc("States/NewYork")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(ny) // tx.Get, NOT ny.Get!
if err != nil {
return err
}
pop, err := doc.DataAt("pop")
if err != nil {
return err
}
return tx.UpdateStruct(ny, []string{"pop"},
State{Population: pop.(float64) + 0.2})
})
if err != nil {
// TODO: Handle error.
}
//]
}

View File

@@ -0,0 +1,148 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// DO NOT EDIT doc.go. Modify internal/doc.template, then run make -C internal.
/*
Package firestore provides a client for reading and writing to a Cloud Firestore
database.
See https://cloud.google.com/firestore/docs for an introduction
to Cloud Firestore and additional help on using the Firestore API.
Creating a Client
To start working with this package, create a client with a project ID:
[NewClient]
CollectionRefs and DocumentRefs
In Firestore, documents are sets of key-value pairs, and collections are groups of
documents. A Firestore database consists of a hierarchy of alternating collections
and documents, referred to by slash-separated paths like
"States/California/Cities/SanFrancisco".
This client is built around references to collections and documents. CollectionRefs
and DocumentRefs are lightweight values that refer to the corresponding database
entities. Creating a ref does not involve any network traffic.
[refs]
Reading
Use DocumentRef.Get to read a document. The result is a DocumentSnapshot.
Call its Data method to obtain the entire document contents as a map.
[docref.Get]
You can also obtain a single field with DataAt, or extract the data into a struct
with DataTo. With the type definition
[structDef]
we can extract the document's data into a value of type State:
[DataTo]
Note that this client supports struct tags beginning with "firestore:" that work like
the tags of the encoding/json package, letting you rename fields, ignore them, or
omit their values when empty.
To retrieve multiple documents from their references in a single call, use
Client.GetAll.
[GetAll]
Writing
For writing individual documents, use the methods on DocumentReference.
Create creates a new document.
[docref.Create]
The first return value is a WriteResult, which contains the time
at which the document was updated.
Create fails if the document exists. Another method, Set, either replaces an existing
document or creates a new one.
[docref.Set]
To update some fields of an existing document, use UpdateMap, UpdateStruct or
UpdatePaths. For UpdateMap, the keys of the map specify which fields to change. The
others are untouched.
[docref.UpdateMap]
For UpdateStruct, you must explicitly provide the fields to update. The field names
must match exactly.
[docref.UpdateStruct]
Use DocumentRef.Delete to delete a document.
[docref.Delete]
Preconditions
You can condition Deletes or Updates on when a document was last changed. Specify
these preconditions as an option to a Delete or Update method. The check and the
write happen atomically with a single RPC.
[LUT-precond]
Here we update a doc only if it hasn't changed since we read it.
You could also do this with a transaction.
To perform multiple writes at once, use a WriteBatch. Its methods chain
for convenience.
WriteBatch.Commit sends the collected writes to the server, where they happen
atomically.
[WriteBatch]
Queries
You can use SQL to select documents from a collection. Begin with the collection, and
build up a query using Select, Where and other methods of Query.
[Query]
Call the Query's Documents method to get an iterator, and use it like
the other Google Cloud Client iterators.
[Documents]
To get all the documents in a collection, you can use the collection itself
as a query.
[CollQuery]
Transactions
Use a transaction to execute reads and writes atomically. All reads must happen
before any writes. Transaction creation, commit, rollback and retry are handled for
you by the Client.RunTransaction method; just provide a function and use the
read and write methods of the Transaction passed to it.
[Transaction]
Authentication
See examples of authorization and authentication at
https://godoc.org/cloud.google.com/go#pkg-examples.
*/
package firestore

View File

@@ -0,0 +1,116 @@
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# snipdoc merges code snippets from Go source files into a template to
# produce another go file (typically doc.go).
#
# Call with one or more .go files and a template file.
#
# awk -f snipmd.awk foo.go bar.go doc.template
#
# In the Go files, start a snippet with
# //[ NAME
# and end it with
# //]
#
# In the template, write
# [NAME]
# on a line by itself to insert the snippet NAME on that line.
#
# The following transformations are made to the Go code:
# - Trailing blank lines are removed.
# - `ELLIPSIS` and `_ = ELLIPSIS` are replaced by `...`
/^[ \t]*\/\/\[/ { # start snippet in Go file
if (inGo()) {
if ($2 == "") {
die("missing snippet name")
}
curSnip = $2
next
}
}
/^[ \t]*\/\/]/ { # end snippet in Go file
if (inGo()) {
if (curSnip != "") {
# Remove all trailing newlines.
gsub(/[\t\n]+$/, "", snips[curSnip])
curSnip = ""
next
} else {
die("//] without corresponding //[")
}
}
}
ENDFILE {
if (curSnip != "") {
die("unclosed snippet: " curSnip)
}
}
/^\[.*\]$/ { # Snippet marker in template file.
if (inTemplate()) {
name = substr($1, 2, length($1)-2)
if (snips[name] == "") {
die("no snippet named " name)
}
printf("%s\n", snips[name])
afterSnip = 1
next
}
}
# Matches every line.
{
if (curSnip != "") {
# If the first line in the snip has no indent, add the indent.
if (snips[curSnip] == "") {
if (index($0, "\t") == 1) {
extraIndent = ""
} else {
extraIndent = "\t"
}
}
line = $0
# Replace ELLIPSIS.
gsub(/_ = ELLIPSIS/, "...", line)
gsub(/ELLIPSIS/, "...", line)
snips[curSnip] = snips[curSnip] extraIndent line "\n"
} else if (inTemplate()) {
afterSnip = 0
# Copy to output.
print
}
}
function inTemplate() {
return match(FILENAME, /\.template$/)
}
function inGo() {
return match(FILENAME, /\.go$/)
}
function die(msg) {
printf("%s:%d: %s\n", FILENAME, FNR, msg) > "/dev/stderr"
exit 1
}

175
vendor/cloud.google.com/go/firestore/mock_test.go generated vendored Normal file
View File

@@ -0,0 +1,175 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
// A simple mock server.
import (
"fmt"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
"golang.org/x/net/context"
)
type mockServer struct {
pb.FirestoreServer
Addr string
reqItems []reqItem
resps []interface{}
}
type reqItem struct {
wantReq proto.Message
adjust func(gotReq proto.Message)
}
func newMockServer() (*mockServer, error) {
srv, err := testutil.NewServer()
if err != nil {
return nil, err
}
mock := &mockServer{Addr: srv.Addr}
pb.RegisterFirestoreServer(srv.Gsrv, mock)
srv.Start()
return mock, nil
}
// addRPC adds a (request, response) pair to the server's list of expected
// interactions. The server will compare the incoming request with wantReq
// using proto.Equal.
//
// Passing nil for wantReq disables the request check.
func (s *mockServer) addRPC(wantReq proto.Message, resp interface{}) {
s.addRPCAdjust(wantReq, resp, nil)
}
// addRPCAdjust is like addRPC, but accepts a function that can be used
// to tweak the requests before comparison, for example to adjust for
// randomness.
func (s *mockServer) addRPCAdjust(wantReq proto.Message, resp interface{}, adjust func(proto.Message)) {
s.reqItems = append(s.reqItems, reqItem{wantReq, adjust})
s.resps = append(s.resps, resp)
}
// popRPC compares the request with the next expected (request, response) pair.
// It returns the response, or an error if the request doesn't match what
// was expected or there are no expected rpcs.
func (s *mockServer) popRPC(gotReq proto.Message) (interface{}, error) {
if len(s.reqItems) == 0 {
panic("out of RPCs")
}
ri := s.reqItems[0]
s.reqItems = s.reqItems[1:]
if ri.wantReq != nil {
if ri.adjust != nil {
ri.adjust(gotReq)
}
if !proto.Equal(gotReq, ri.wantReq) {
return nil, fmt.Errorf("mockServer: bad request\ngot: %T\n%+v\nwant: %T\n%+v",
gotReq, gotReq, ri.wantReq, ri.wantReq)
}
}
resp := s.resps[0]
s.resps = s.resps[1:]
if err, ok := resp.(error); ok {
return nil, err
}
return resp, nil
}
func (s *mockServer) reset() {
s.reqItems = nil
s.resps = nil
}
func (s *mockServer) GetDocument(_ context.Context, req *pb.GetDocumentRequest) (*pb.Document, error) {
res, err := s.popRPC(req)
if err != nil {
return nil, err
}
return res.(*pb.Document), nil
}
func (s *mockServer) Commit(_ context.Context, req *pb.CommitRequest) (*pb.CommitResponse, error) {
res, err := s.popRPC(req)
if err != nil {
return nil, err
}
return res.(*pb.CommitResponse), nil
}
func (s *mockServer) BatchGetDocuments(req *pb.BatchGetDocumentsRequest, bs pb.Firestore_BatchGetDocumentsServer) error {
res, err := s.popRPC(req)
if err != nil {
return err
}
responses := res.([]interface{})
for _, res := range responses {
switch res := res.(type) {
case *pb.BatchGetDocumentsResponse:
if err := bs.Send(res); err != nil {
return err
}
case error:
return res
default:
panic(fmt.Sprintf("bad response type in BatchGetDocuments: %+v", res))
}
}
return nil
}
func (s *mockServer) RunQuery(req *pb.RunQueryRequest, qs pb.Firestore_RunQueryServer) error {
res, err := s.popRPC(req)
if err != nil {
return err
}
responses := res.([]interface{})
for _, res := range responses {
switch res := res.(type) {
case *pb.RunQueryResponse:
if err := qs.Send(res); err != nil {
return err
}
case error:
return res
default:
panic(fmt.Sprintf("bad response type in RunQuery: %+v", res))
}
}
return nil
}
func (s *mockServer) BeginTransaction(_ context.Context, req *pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) {
res, err := s.popRPC(req)
if err != nil {
return nil, err
}
return res.(*pb.BeginTransactionResponse), nil
}
func (s *mockServer) Rollback(_ context.Context, req *pb.RollbackRequest) (*empty.Empty, error) {
res, err := s.popRPC(req)
if err != nil {
return nil, err
}
return res.(*empty.Empty), nil
}

182
vendor/cloud.google.com/go/firestore/options.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
)
// A Precondition modifies a Firestore update or delete operation.
type Precondition interface {
// Returns the corresponding Precondition proto.
preconditionProto() (*pb.Precondition, error)
}
// Exists returns a Precondition that checks for the existence or non-existence
// of a resource before writing to it. If the check fails, the write does not occur.
func Exists(b bool) Precondition { return exists(b) }
type exists bool
func (e exists) preconditionProto() (*pb.Precondition, error) {
return &pb.Precondition{
ConditionType: &pb.Precondition_Exists{bool(e)},
}, nil
}
func (e exists) String() string { return fmt.Sprintf("Exists(%t)", e) }
// LastUpdateTime returns a Precondition that checks that a resource must exist and
// must have last been updated at the given time. If the check fails, the write
// does not occur.
func LastUpdateTime(t time.Time) Precondition { return lastUpdateTime(t) }
type lastUpdateTime time.Time
func (u lastUpdateTime) preconditionProto() (*pb.Precondition, error) {
ts, err := ptypes.TimestampProto(time.Time(u))
if err != nil {
return nil, err
}
return &pb.Precondition{
ConditionType: &pb.Precondition_UpdateTime{ts},
}, nil
}
func (u lastUpdateTime) String() string { return fmt.Sprintf("LastUpdateTime(%s)", time.Time(u)) }
func processPreconditionsForDelete(preconds []Precondition) (*pb.Precondition, error) {
// At most one option permitted.
switch len(preconds) {
case 0:
return nil, nil
case 1:
return preconds[0].preconditionProto()
default:
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
}
}
func processPreconditionsForUpdate(preconds []Precondition) (*pb.Precondition, error) {
// At most one option permitted, and it cannot be Exists.
switch len(preconds) {
case 0:
// If the user doesn't provide any options, default to Exists(true).
return exists(true).preconditionProto()
case 1:
if _, ok := preconds[0].(exists); ok {
return nil, errors.New("Cannot use Exists with Update")
}
return preconds[0].preconditionProto()
default:
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
}
}
func processPreconditionsForVerify(preconds []Precondition) (*pb.Precondition, error) {
// At most one option permitted.
switch len(preconds) {
case 0:
return nil, nil
case 1:
return preconds[0].preconditionProto()
default:
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
}
}
// A SetOption modifies a Firestore set operation.
type SetOption interface {
fieldPaths() (fps []FieldPath, all bool, err error)
}
// MergeAll is a SetOption that causes all the field paths given in the data argument
// to Set to be overwritten. It is not supported for struct data.
var MergeAll SetOption = merge{all: true}
// Merge returns a SetOption that causes only the given field paths to be
// overwritten. Other fields on the existing document will be untouched. It is an
// error if a provided field path does not refer to a value in the data passed to
// Set.
//
// Each element of fieldPaths must be a single field or a dot-separated sequence of
// fields, none of which contain the runes "˜*/[]". Use MergePaths instead for such
// paths.
func Merge(fieldPaths ...string) SetOption {
fps, err := parseDotSeparatedStrings(fieldPaths)
if err != nil {
return merge{err: err}
}
return merge{paths: fps}
}
// MergePaths returns a SetOption that causes only the given field paths to be
// overwritten. Other fields on the existing document will be untouched. It is an
// error if a provided field path does not refer to a value in the data passed to
// Set.
func MergePaths(fps ...FieldPath) SetOption {
for _, fp := range fps {
if err := fp.validate(); err != nil {
return merge{err: err}
}
}
return merge{paths: fps}
}
type merge struct {
all bool
paths []FieldPath
err error
}
func (m merge) String() string {
if m.err != nil {
return fmt.Sprintf("<Merge error: %v>", m.err)
}
if m.all {
return "MergeAll"
}
return fmt.Sprintf("Merge(%+v)", m.paths)
}
func (m merge) fieldPaths() (fps []FieldPath, all bool, err error) {
if m.err != nil {
return nil, false, m.err
}
if err := checkNoDupOrPrefix(m.paths); err != nil {
return nil, false, err
}
if m.all {
return nil, true, nil
}
return m.paths, false, nil
}
func processSetOptions(opts []SetOption) (fps []FieldPath, all bool, err error) {
switch len(opts) {
case 0:
return nil, false, nil
case 1:
return opts[0].fieldPaths()
default:
return nil, false, fmt.Errorf("conflicting options: %+v", opts)
}
}

155
vendor/cloud.google.com/go/firestore/options_test.go generated vendored Normal file
View File

@@ -0,0 +1,155 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"testing"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
)
func TestProcessPreconditionsForVerify(t *testing.T) {
for _, test := range []struct {
in []Precondition
want *pb.Precondition
wantErr bool
}{
{
in: nil,
want: nil,
},
{
in: []Precondition{},
want: nil,
},
{
in: []Precondition{Exists(true)},
want: &pb.Precondition{&pb.Precondition_Exists{true}},
},
{
in: []Precondition{LastUpdateTime(aTime)},
want: &pb.Precondition{&pb.Precondition_UpdateTime{aTimestamp}},
},
{
in: []Precondition{Exists(true), LastUpdateTime(aTime)},
wantErr: true,
},
{
in: []Precondition{Exists(true), Exists(true)},
wantErr: true,
},
} {
got, err := processPreconditionsForVerify(test.in)
switch {
case test.wantErr && err == nil:
t.Errorf("%v: got nil, want error", test.in)
case !test.wantErr && err != nil:
t.Errorf("%v: got <%v>, want no error", test.in, err)
case !test.wantErr && err == nil && !testEqual(got, test.want):
t.Errorf("%v: got %+v, want %v", test.in, got, test.want)
}
}
}
func TestProcessPreconditionsForDelete(t *testing.T) {
for _, test := range []struct {
in []Precondition
want *pb.Precondition
wantErr bool
}{
{
in: nil,
want: nil,
},
{
in: []Precondition{},
want: nil,
},
{
in: []Precondition{Exists(true)},
want: &pb.Precondition{&pb.Precondition_Exists{true}},
},
{
in: []Precondition{LastUpdateTime(aTime)},
want: &pb.Precondition{&pb.Precondition_UpdateTime{aTimestamp}},
},
{
in: []Precondition{Exists(true), LastUpdateTime(aTime)},
wantErr: true,
},
{
in: []Precondition{Exists(true), Exists(true)},
wantErr: true,
},
} {
got, err := processPreconditionsForDelete(test.in)
switch {
case test.wantErr && err == nil:
t.Errorf("%v: got nil, want error", test.in)
case !test.wantErr && err != nil:
t.Errorf("%v: got <%v>, want no error", test.in, err)
case !test.wantErr && err == nil && !testEqual(got, test.want):
t.Errorf("%v: got %+v, want %v", test.in, got, test.want)
}
}
}
func TestProcessPreconditionsForUpdate(t *testing.T) {
for _, test := range []struct {
in []Precondition
want *pb.Precondition
wantErr bool
}{
{
in: nil,
want: &pb.Precondition{&pb.Precondition_Exists{true}},
},
{
in: []Precondition{},
want: &pb.Precondition{&pb.Precondition_Exists{true}},
},
{
in: []Precondition{Exists(true)},
wantErr: true,
},
{
in: []Precondition{Exists(false)},
wantErr: true,
},
{
in: []Precondition{LastUpdateTime(aTime)},
want: &pb.Precondition{&pb.Precondition_UpdateTime{aTimestamp}},
},
{
in: []Precondition{Exists(true), LastUpdateTime(aTime)},
wantErr: true,
},
{
in: []Precondition{Exists(true), Exists(true)},
wantErr: true,
},
} {
got, err := processPreconditionsForUpdate(test.in)
switch {
case test.wantErr && err == nil:
t.Errorf("%v: got nil, want error", test.in)
case !test.wantErr && err != nil:
t.Errorf("%v: got <%v>, want no error", test.in, err)
case !test.wantErr && err == nil && !testEqual(got, test.want):
t.Errorf("%v: got %+v, want %v", test.in, got, test.want)
}
}
}

447
vendor/cloud.google.com/go/firestore/query.go generated vendored Normal file
View File

@@ -0,0 +1,447 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"io"
"math"
"reflect"
"golang.org/x/net/context"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes/wrappers"
"google.golang.org/api/iterator"
)
// Query represents a Firestore query.
//
// Query values are immutable. Each Query method creates
// a new Query; it does not modify the old.
type Query struct {
c *Client
parentPath string // path of the collection's parent
collectionID string
selection []FieldPath
filters []filter
orders []order
offset int32
limit *wrappers.Int32Value
startVals, endVals []interface{}
startBefore, endBefore bool
err error
}
// DocumentID is the special field name representing the ID of a document
// in queries.
const DocumentID = "__name__"
// Select returns a new Query that specifies the field paths
// to return from the result documents.
func (q Query) Select(fieldPaths ...string) Query {
var fps []FieldPath
for _, s := range fieldPaths {
fp, err := parseDotSeparatedString(s)
if err != nil {
q.err = err
return q
}
fps = append(fps, fp)
}
if fps == nil {
q.selection = []FieldPath{{DocumentID}}
} else {
q.selection = fps
}
return q
}
// SelectPaths returns a new Query that specifies the field paths
// to return from the result documents.
func (q Query) SelectPaths(fieldPaths ...FieldPath) Query {
q.selection = fieldPaths
return q
}
// Where returns a new Query that filters the set of results.
// A Query can have multiple filters.
func (q Query) Where(fieldPath, op string, value interface{}) Query {
fp, err := parseDotSeparatedString(fieldPath)
if err != nil {
q.err = err
return q
}
q.filters = append(append([]filter(nil), q.filters...), filter{fp, op, value})
return q
}
// WherePath returns a new Query that filters the set of results.
// A Query can have multiple filters.
func (q Query) WherePath(fp FieldPath, op string, value interface{}) Query {
q.filters = append(append([]filter(nil), q.filters...), filter{fp, op, value})
return q
}
// Direction is the sort direction for result ordering.
type Direction int32
const (
// Asc sorts results from smallest to largest.
Asc Direction = Direction(pb.StructuredQuery_ASCENDING)
// Desc sorts results from largest to smallest.
Desc Direction = Direction(pb.StructuredQuery_DESCENDING)
)
// OrderBy returns a new Query that specifies the order in which results are
// returned. A Query can have multiple OrderBy/OrderByPath specifications. OrderBy
// appends the specification to the list of existing ones.
//
// To order by document name, use the special field path DocumentID.
func (q Query) OrderBy(fieldPath string, dir Direction) Query {
fp, err := parseDotSeparatedString(fieldPath)
if err != nil {
q.err = err
return q
}
q.orders = append(append([]order(nil), q.orders...), order{fp, dir})
return q
}
// OrderByPath returns a new Query that specifies the order in which results are
// returned. A Query can have multiple OrderBy/OrderByPath specifications.
// OrderByPath appends the specification to the list of existing ones.
func (q Query) OrderByPath(fp FieldPath, dir Direction) Query {
q.orders = append(append([]order(nil), q.orders...), order{fp, dir})
return q
}
// Offset returns a new Query that specifies the number of initial results to skip.
// It must not be negative.
func (q Query) Offset(n int) Query {
q.offset = trunc32(n)
return q
}
// Limit returns a new Query that specifies the maximum number of results to return.
// It must not be negative.
func (q Query) Limit(n int) Query {
q.limit = &wrappers.Int32Value{trunc32(n)}
return q
}
// StartAt returns a new Query that specifies that results should start at
// the document with the given field values. The field path corresponding to
// each value is taken from the corresponding OrderBy call. For example, in
// q.OrderBy("X", Asc).OrderBy("Y", Desc).StartAt(1, 2)
// results will begin at the first document where X = 1 and Y = 2.
//
// If an OrderBy call uses the special DocumentID field path, the corresponding value
// should be the document ID relative to the query's collection. For example, to
// start at the document "NewYork" in the "States" collection, write
//
// client.Collection("States").OrderBy(DocumentID, firestore.Asc).StartAt("NewYork")
//
// Calling StartAt overrides a previous call to StartAt or StartAfter.
func (q Query) StartAt(fieldValues ...interface{}) Query {
q.startVals, q.startBefore = fieldValues, true
return q
}
// StartAfter returns a new Query that specifies that results should start just after
// the document with the given field values. See Query.StartAt for more information.
//
// Calling StartAfter overrides a previous call to StartAt or StartAfter.
func (q Query) StartAfter(fieldValues ...interface{}) Query {
q.startVals, q.startBefore = fieldValues, false
return q
}
// EndAt returns a new Query that specifies that results should end at the
// document with the given field values. See Query.StartAt for more information.
//
// Calling EndAt overrides a previous call to EndAt or EndBefore.
func (q Query) EndAt(fieldValues ...interface{}) Query {
q.endVals, q.endBefore = fieldValues, false
return q
}
// EndBefore returns a new Query that specifies that results should end just before
// the document with the given field values. See Query.StartAt for more information.
//
// Calling EndBefore overrides a previous call to EndAt or EndBefore.
func (q Query) EndBefore(fieldValues ...interface{}) Query {
q.endVals, q.endBefore = fieldValues, true
return q
}
func (q Query) query() *Query { return &q }
func (q Query) toProto() (*pb.StructuredQuery, error) {
if q.err != nil {
return nil, q.err
}
if q.collectionID == "" {
return nil, errors.New("firestore: query created without CollectionRef")
}
p := &pb.StructuredQuery{
From: []*pb.StructuredQuery_CollectionSelector{{CollectionId: q.collectionID}},
Offset: q.offset,
Limit: q.limit,
}
if len(q.selection) > 0 {
p.Select = &pb.StructuredQuery_Projection{}
for _, fp := range q.selection {
if err := fp.validate(); err != nil {
return nil, err
}
p.Select.Fields = append(p.Select.Fields, fref(fp))
}
}
// If there is only filter, use it directly. Otherwise, construct
// a CompositeFilter.
if len(q.filters) == 1 {
pf, err := q.filters[0].toProto()
if err != nil {
return nil, err
}
p.Where = pf
} else if len(q.filters) > 1 {
cf := &pb.StructuredQuery_CompositeFilter{
Op: pb.StructuredQuery_CompositeFilter_AND,
}
p.Where = &pb.StructuredQuery_Filter{
FilterType: &pb.StructuredQuery_Filter_CompositeFilter{cf},
}
for _, f := range q.filters {
pf, err := f.toProto()
if err != nil {
return nil, err
}
cf.Filters = append(cf.Filters, pf)
}
}
for _, ord := range q.orders {
po, err := ord.toProto()
if err != nil {
return nil, err
}
p.OrderBy = append(p.OrderBy, po)
}
// StartAt and EndAt must have values that correspond exactly to the explicit order-by fields.
if len(q.startVals) != 0 {
vals, err := q.toPositionValues(q.startVals)
if err != nil {
return nil, err
}
p.StartAt = &pb.Cursor{Values: vals, Before: q.startBefore}
}
if len(q.endVals) != 0 {
vals, err := q.toPositionValues(q.endVals)
if err != nil {
return nil, err
}
p.EndAt = &pb.Cursor{Values: vals, Before: q.endBefore}
}
return p, nil
}
// toPositionValues converts the field values to protos.
func (q *Query) toPositionValues(fieldValues []interface{}) ([]*pb.Value, error) {
if len(fieldValues) != len(q.orders) {
return nil, errors.New("firestore: number of field values in StartAt/StartAfter/EndAt/EndBefore does not match number of OrderBy fields")
}
vals := make([]*pb.Value, len(fieldValues))
var err error
for i, ord := range q.orders {
fval := fieldValues[i]
if len(ord.fieldPath) == 1 && ord.fieldPath[0] == DocumentID {
docID, ok := fval.(string)
if !ok {
return nil, fmt.Errorf("firestore: expected doc ID for DocumentID field, got %T", fval)
}
vals[i] = &pb.Value{&pb.Value_ReferenceValue{q.parentPath + "/documents/" + q.collectionID + "/" + docID}}
} else {
vals[i], err = toProtoValue(reflect.ValueOf(fval))
if err != nil {
return nil, err
}
}
}
return vals, nil
}
type filter struct {
fieldPath FieldPath
op string
value interface{}
}
func (f filter) toProto() (*pb.StructuredQuery_Filter, error) {
if err := f.fieldPath.validate(); err != nil {
return nil, err
}
var op pb.StructuredQuery_FieldFilter_Operator
switch f.op {
case "<":
op = pb.StructuredQuery_FieldFilter_LESS_THAN
case "<=":
op = pb.StructuredQuery_FieldFilter_LESS_THAN_OR_EQUAL
case ">":
op = pb.StructuredQuery_FieldFilter_GREATER_THAN
case ">=":
op = pb.StructuredQuery_FieldFilter_GREATER_THAN_OR_EQUAL
case "==":
op = pb.StructuredQuery_FieldFilter_EQUAL
default:
return nil, fmt.Errorf("firestore: invalid operator %q", f.op)
}
val, err := toProtoValue(reflect.ValueOf(f.value))
if err != nil {
return nil, err
}
return &pb.StructuredQuery_Filter{
FilterType: &pb.StructuredQuery_Filter_FieldFilter{
&pb.StructuredQuery_FieldFilter{
Field: fref(f.fieldPath),
Op: op,
Value: val,
},
},
}, nil
}
type order struct {
fieldPath FieldPath
dir Direction
}
func (r order) toProto() (*pb.StructuredQuery_Order, error) {
if err := r.fieldPath.validate(); err != nil {
return nil, err
}
return &pb.StructuredQuery_Order{
Field: fref(r.fieldPath),
Direction: pb.StructuredQuery_Direction(r.dir),
}, nil
}
func fref(fp FieldPath) *pb.StructuredQuery_FieldReference {
return &pb.StructuredQuery_FieldReference{fp.toServiceFieldPath()}
}
func trunc32(i int) int32 {
if i > math.MaxInt32 {
i = math.MaxInt32
}
return int32(i)
}
// Documents returns an iterator over the query's resulting documents.
func (q Query) Documents(ctx context.Context) *DocumentIterator {
return &DocumentIterator{
ctx: withResourceHeader(ctx, q.c.path()),
q: &q,
err: checkTransaction(ctx),
}
}
// DocumentIterator is an iterator over documents returned by a query.
type DocumentIterator struct {
ctx context.Context
q *Query
tid []byte // transaction ID, if any
streamClient pb.Firestore_RunQueryClient
err error
}
// Next returns the next result. Its second return value is iterator.Done if there
// are no more results. Once Next returns Done, all subsequent calls will return
// Done.
func (it *DocumentIterator) Next() (*DocumentSnapshot, error) {
if it.err != nil {
return nil, it.err
}
client := it.q.c
if it.streamClient == nil {
sq, err := it.q.toProto()
if err != nil {
it.err = err
return nil, err
}
req := &pb.RunQueryRequest{
Parent: it.q.parentPath,
QueryType: &pb.RunQueryRequest_StructuredQuery{sq},
}
if it.tid != nil {
req.ConsistencySelector = &pb.RunQueryRequest_Transaction{it.tid}
}
it.streamClient, it.err = client.c.RunQuery(it.ctx, req)
if it.err != nil {
return nil, it.err
}
}
var res *pb.RunQueryResponse
var err error
for {
res, err = it.streamClient.Recv()
if err == io.EOF {
err = iterator.Done
}
if err != nil {
it.err = err
return nil, it.err
}
if res.Document != nil {
break
}
// No document => partial progress; keep receiving.
}
docRef, err := pathToDoc(res.Document.Name, client)
if err != nil {
it.err = err
return nil, err
}
doc, err := newDocumentSnapshot(docRef, res.Document, client)
if err != nil {
it.err = err
return nil, err
}
return doc, nil
}
// GetAll returns all the documents remaining from the iterator.
func (it *DocumentIterator) GetAll() ([]*DocumentSnapshot, error) {
var docs []*DocumentSnapshot
for {
doc, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}
docs = append(docs, doc)
}
return docs, nil
}
// TODO(jba): Does the iterator need a Stop or Close method? I don't think so--
// I don't think the client can terminate a streaming receive except perhaps
// by cancelling the context, and the user can do that themselves if they wish.
// Find out for sure.

381
vendor/cloud.google.com/go/firestore/query_test.go generated vendored Normal file
View File

@@ -0,0 +1,381 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"testing"
"golang.org/x/net/context"
"cloud.google.com/go/internal/pretty"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes/wrappers"
)
func TestQueryToProto(t *testing.T) {
c := &Client{}
coll := c.Collection("C")
q := coll.Query
aFilter, err := filter{[]string{"a"}, ">", 5}.toProto()
if err != nil {
t.Fatal(err)
}
bFilter, err := filter{[]string{"b"}, "<", "foo"}.toProto()
if err != nil {
t.Fatal(err)
}
slashStarFilter, err := filter{[]string{"/", "*"}, ">", 5}.toProto()
if err != nil {
t.Fatal(err)
}
type S struct {
A int `firestore:"a"`
}
for _, test := range []struct {
in Query
want *pb.StructuredQuery
}{
{
in: q.Select(),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("__name__")},
},
},
},
{
in: q.Select("a", "b"),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("a"), fref1("b")},
},
},
},
{
in: q.Select("a", "b").Select("c"), // last wins
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("c")},
},
},
},
{
in: q.SelectPaths([]string{"*"}, []string{"/"}),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("*"), fref1("/")},
},
},
},
{
in: q.Where("a", ">", 5),
want: &pb.StructuredQuery{Where: aFilter},
},
{
in: q.Where("a", ">", 5).Where("b", "<", "foo"),
want: &pb.StructuredQuery{
Where: &pb.StructuredQuery_Filter{
&pb.StructuredQuery_Filter_CompositeFilter{
&pb.StructuredQuery_CompositeFilter{
Op: pb.StructuredQuery_CompositeFilter_AND,
Filters: []*pb.StructuredQuery_Filter{
aFilter, bFilter,
},
},
},
},
},
},
{
in: q.WherePath([]string{"/", "*"}, ">", 5),
want: &pb.StructuredQuery{Where: slashStarFilter},
},
{
in: q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("b"), pb.StructuredQuery_ASCENDING},
{fref1("a"), pb.StructuredQuery_DESCENDING},
{fref1("~"), pb.StructuredQuery_ASCENDING},
},
},
},
{
in: q.Offset(2).Limit(3),
want: &pb.StructuredQuery{
Offset: 2,
Limit: &wrappers.Int32Value{3},
},
},
{
in: q.Offset(2).Limit(3).Limit(4).Offset(5), // last wins
want: &pb.StructuredQuery{
Offset: 5,
Limit: &wrappers.Int32Value{4},
},
},
{
in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7)},
Before: true,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(9)},
Before: true,
},
},
},
{
in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7)},
Before: true,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(9)},
Before: true,
},
},
},
{
in: q.OrderBy("a", Asc).StartAfter(7).EndAt(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7)},
Before: false,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(9)},
Before: false,
},
},
},
{
in: q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
{fref1("b"), pb.StructuredQuery_DESCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), intval(8)},
Before: false,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(9), intval(10)},
Before: false,
},
},
},
{
// last of StartAt/After wins, same for End
in: q.OrderBy("a", Asc).
StartAfter(1).StartAt(2).
EndAt(3).EndBefore(4),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(2)},
Before: true,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(4)},
Before: true,
},
},
},
} {
got, err := test.in.toProto()
if err != nil {
t.Fatalf("%+v: %v", test.in, err)
}
test.want.From = []*pb.StructuredQuery_CollectionSelector{{CollectionId: "C"}}
if !testEqual(got, test.want) {
t.Errorf("%+v: got\n%v\nwant\n%v", test.in, pretty.Value(got), pretty.Value(test.want))
}
}
}
func fref1(s string) *pb.StructuredQuery_FieldReference {
return fref([]string{s})
}
func TestQueryToProtoErrors(t *testing.T) {
q := (&Client{}).Collection("C").Query
for _, query := range []Query{
Query{}, // no collection ID
q.Where("x", "!=", 1), // invalid operator
q.Where("~", ">", 1), // invalid path
q.WherePath([]string{"*", ""}, ">", 1), // invalid path
q.StartAt(1), // no OrderBy
q.StartAt(2).OrderBy("x", Asc).OrderBy("y", Desc), // wrong # OrderBy
q.Select("*"), // invalid path
q.SelectPaths([]string{"/", "", "~"}), // invalid path
q.OrderBy("[", Asc), // invalid path
q.OrderByPath([]string{""}, Desc), // invalid path
} {
_, err := query.toProto()
if err == nil {
t.Errorf("%+v: got nil, want error", query)
}
}
}
func TestQueryMethodsDoNotModifyReceiver(t *testing.T) {
var empty Query
q := Query{}
_ = q.Select("a", "b")
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
q1 := q.Where("a", ">", 3)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
// Extra check because Where appends to a slice.
q1before := q.Where("a", ">", 3) // same as q1
_ = q1.Where("b", "<", "foo")
if !testEqual(q1, q1before) {
t.Errorf("got %+v, want %+v", q1, q1before)
}
q = Query{}
q1 = q.OrderBy("a", Asc)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
// Extra check because Where appends to a slice.
q1before = q.OrderBy("a", Asc) // same as q1
_ = q1.OrderBy("b", Desc)
if !testEqual(q1, q1before) {
t.Errorf("got %+v, want %+v", q1, q1before)
}
q = Query{}
_ = q.Offset(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
_ = q.Limit(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
_ = q.StartAt(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
_ = q.StartAfter(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
_ = q.EndAt(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
q = Query{}
_ = q.EndBefore(5)
if !testEqual(q, empty) {
t.Errorf("got %+v, want empty", q)
}
}
func TestQueryFromCollectionRef(t *testing.T) {
c := &Client{}
coll := c.Collection("C")
got := coll.Select("x").Offset(8)
want := Query{
c: c,
parentPath: c.path(),
collectionID: "C",
selection: []FieldPath{{"x"}},
offset: 8,
}
if !testEqual(got, want) {
t.Fatalf("got %+v, want %+v", got, want)
}
}
func TestQueryGetAll(t *testing.T) {
// This implicitly tests DocumentIterator as well.
const dbPath = "projects/projectID/databases/(default)"
ctx := context.Background()
c, srv := newMock(t)
docNames := []string{"C/a", "C/b"}
wantPBDocs := []*pb.Document{
{
Name: dbPath + "/documents/" + docNames[0],
CreateTime: aTimestamp,
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(2)},
},
{
Name: dbPath + "/documents/" + docNames[1],
CreateTime: aTimestamp2,
UpdateTime: aTimestamp3,
Fields: map[string]*pb.Value{"f": intval(1)},
},
}
srv.addRPC(nil, []interface{}{
&pb.RunQueryResponse{Document: wantPBDocs[0]},
&pb.RunQueryResponse{Document: wantPBDocs[1]},
})
gotDocs, err := c.Collection("C").Documents(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if got, want := len(gotDocs), len(wantPBDocs); got != want {
t.Errorf("got %d docs, wanted %d", got, want)
}
for i, got := range gotDocs {
want, err := newDocumentSnapshot(c.Doc(docNames[i]), wantPBDocs[i], c)
if err != nil {
t.Fatal(err)
}
if !testEqual(got, want) {
// avoid writing a cycle
got.c = nil
want.c = nil
t.Errorf("#%d: got %+v, want %+v", i, pretty.Value(got), pretty.Value(want))
}
}
}

View File

@@ -0,0 +1,23 @@
{
"comment": "Field path test suite. Backslash must be escaped in JSON strings.",
"good": [
["a", "a"],
["__x__", "__x__"],
["aBc0_d1", "aBc0_d1"],
["a.b", "a", "b"],
["`a`", "a"],
["`a`.b", "a", "b"],
["`a`.`b`.c", "a", "b", "c"],
["`a.b`.c", "a.b", "c"],
["`..`.`...`", "..", "..."],
["` `", " "],
["`\t\t`.` x\t`", "\t\t", " x\t"],
["`\b\f\r`", "\b\f\r"],
["`a\\`b`.`c\\\\d`", "a`b", "c\\d"],
["`\\\\`.`\\`\\``", "\\", "``"]
],
"bad": ["", " ", "\t", "a.", ".a", "a.b.", "a..b",
"`", "``", "`a", "a`", "a`b", "`a`b", "a`b`", "`a`.b`c`",
"\\", "\\`"]
}

256
vendor/cloud.google.com/go/firestore/to_value.go generated vendored Normal file
View File

@@ -0,0 +1,256 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
"cloud.google.com/go/internal/fields"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
"google.golang.org/genproto/googleapis/type/latlng"
)
var nullValue = &pb.Value{&pb.Value_NullValue{}}
var (
typeOfByteSlice = reflect.TypeOf([]byte{})
typeOfGoTime = reflect.TypeOf(time.Time{})
typeOfLatLng = reflect.TypeOf((*latlng.LatLng)(nil))
typeOfDocumentRef = reflect.TypeOf((*DocumentRef)(nil))
)
// toProtoValue converts a Go value to a Firestore Value protobuf.
// Some corner cases:
// - All nils (nil interface, nil slice, nil map, nil pointer) are converted to
// a NullValue (not a nil *pb.Value). toProtoValue never returns (nil, nil).
// - An error is returned for uintptr, uint and uint64, because Firestore uses
// an int64 to represent integral values, and those types can't be properly
// represented in an int64.
// - An error is returned for the special Delete value.
func toProtoValue(v reflect.Value) (*pb.Value, error) {
if !v.IsValid() {
return nullValue, nil
}
vi := v.Interface()
if vi == Delete {
return nil, errors.New("firestore: cannot use Delete in value")
}
switch x := vi.(type) {
case []byte:
return &pb.Value{&pb.Value_BytesValue{x}}, nil
case time.Time:
ts, err := ptypes.TimestampProto(x)
if err != nil {
return nil, err
}
return &pb.Value{&pb.Value_TimestampValue{ts}}, nil
case *latlng.LatLng:
if x == nil {
// gRPC doesn't like nil oneofs. Use NullValue.
return nullValue, nil
}
return &pb.Value{&pb.Value_GeoPointValue{x}}, nil
case *DocumentRef:
if x == nil {
// gRPC doesn't like nil oneofs. Use NullValue.
return nullValue, nil
}
return &pb.Value{&pb.Value_ReferenceValue{x.Path}}, nil
// Do not add bool, string, int, etc. to this switch; leave them in the
// reflect-based switch below. Moving them here would drop support for
// types whose underlying types are those primitives.
// E.g. Given "type mybool bool", an ordinary type switch on bool will
// not catch a mybool, but the reflect.Kind of a mybool is reflect.Bool.
}
switch v.Kind() {
case reflect.Bool:
return &pb.Value{&pb.Value_BooleanValue{v.Bool()}}, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return &pb.Value{&pb.Value_IntegerValue{v.Int()}}, nil
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
return &pb.Value{&pb.Value_IntegerValue{int64(v.Uint())}}, nil
case reflect.Float32, reflect.Float64:
return &pb.Value{&pb.Value_DoubleValue{v.Float()}}, nil
case reflect.String:
return &pb.Value{&pb.Value_StringValue{v.String()}}, nil
case reflect.Slice:
return sliceToProtoValue(v)
case reflect.Map:
return mapToProtoValue(v)
case reflect.Struct:
return structToProtoValue(v)
case reflect.Ptr:
if v.IsNil() {
return nullValue, nil
}
return toProtoValue(v.Elem())
case reflect.Interface:
if v.NumMethod() == 0 { // empty interface: recurse on its contents
return toProtoValue(v.Elem())
}
fallthrough // any other interface value is an error
default:
return nil, fmt.Errorf("firestore: cannot convert type %s to value", v.Type())
}
}
func sliceToProtoValue(v reflect.Value) (*pb.Value, error) {
// A nil slice is converted to a null value.
if v.IsNil() {
return nullValue, nil
}
vals := make([]*pb.Value, v.Len())
for i := 0; i < v.Len(); i++ {
val, err := toProtoValue(v.Index(i))
if err != nil {
return nil, err
}
vals[i] = val
}
return &pb.Value{&pb.Value_ArrayValue{&pb.ArrayValue{vals}}}, nil
}
func mapToProtoValue(v reflect.Value) (*pb.Value, error) {
if v.Type().Key().Kind() != reflect.String {
return nil, errors.New("firestore: map key type must be string")
}
// A nil map is converted to a null value.
if v.IsNil() {
return nullValue, nil
}
m := map[string]*pb.Value{}
for _, k := range v.MapKeys() {
mi := v.MapIndex(k)
if mi.Interface() == ServerTimestamp {
continue
}
val, err := toProtoValue(mi)
if err != nil {
return nil, err
}
m[k.String()] = val
}
return &pb.Value{&pb.Value_MapValue{&pb.MapValue{m}}}, nil
}
func structToProtoValue(v reflect.Value) (*pb.Value, error) {
m := map[string]*pb.Value{}
fields, err := fieldCache.Fields(v.Type())
if err != nil {
return nil, err
}
for _, f := range fields {
fv := v.FieldByIndex(f.Index)
opts := f.ParsedTag.(tagOptions)
if opts.serverTimestamp {
continue
}
if opts.omitEmpty && isEmptyValue(fv) {
continue
}
val, err := toProtoValue(fv)
if err != nil {
return nil, err
}
m[f.Name] = val
}
return &pb.Value{&pb.Value_MapValue{&pb.MapValue{m}}}, nil
}
type tagOptions struct {
omitEmpty bool // do not marshal value if empty
serverTimestamp bool // set time.Time to server timestamp on write
}
// parseTag interprets firestore struct field tags.
func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
name, keep, opts, err := parseStandardTag("firestore", t)
if err != nil {
return "", false, nil, fmt.Errorf("firestore: %v", err)
}
tagOpts := tagOptions{}
for _, opt := range opts {
switch opt {
case "omitempty":
tagOpts.omitEmpty = true
case "serverTimestamp":
tagOpts.serverTimestamp = true
default:
return "", false, nil, fmt.Errorf("firestore: unknown tag option: %q", opt)
}
}
return name, keep, tagOpts, nil
}
// parseStandardTag extracts the sub-tag named by key, then parses it using the
// de facto standard format introduced in encoding/json:
// "-" means "ignore this tag". It must occur by itself. (parseStandardTag returns an error
// in this case, whereas encoding/json accepts the "-" even if it is not alone.)
// "<name>" provides an alternative name for the field
// "<name>,opt1,opt2,..." specifies options after the name.
// The options are returned as a []string.
//
// TODO(jba): move this into the fields package, and use it elsewhere, like bigquery.
func parseStandardTag(key string, t reflect.StructTag) (name string, keep bool, options []string, err error) {
s := t.Get(key)
parts := strings.Split(s, ",")
if parts[0] == "-" {
if len(parts) > 1 {
return "", false, nil, errors.New(`"-" field tag with options`)
}
return "", false, nil, nil
}
return parts[0], true, parts[1:], nil
}
// isLeafType determines whether or not a type is a 'leaf type'
// and should not be recursed into, but considered one field.
func isLeafType(t reflect.Type) bool {
return t == typeOfGoTime || t == typeOfLatLng
}
var fieldCache = fields.NewCache(parseTag, nil, isLeafType)
// isEmptyValue is taken from the encoding/json package in the
// standard library.
// TODO(jba): move to the fields package
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
if v.Type() == typeOfGoTime {
return v.Interface().(time.Time).IsZero()
}
return false
}

219
vendor/cloud.google.com/go/firestore/to_value_test.go generated vendored Normal file
View File

@@ -0,0 +1,219 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"fmt"
"reflect"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/genproto/googleapis/type/latlng"
)
type testStruct1 struct {
B bool
I int
U uint32
F float64
S string
Y []byte
T time.Time
G *latlng.LatLng
L []int
M map[string]int
P *int
}
var (
p = new(int)
testVal1 = testStruct1{
B: true,
I: 1,
U: 2,
F: 3.0,
S: "four",
Y: []byte{5},
T: tm,
G: ll,
L: []int{6},
M: map[string]int{"a": 7},
P: p,
}
mapVal1 = mapval(map[string]*pb.Value{
"B": boolval(true),
"I": intval(1),
"U": intval(2),
"F": floatval(3),
"S": &pb.Value{&pb.Value_StringValue{"four"}},
"Y": bytesval([]byte{5}),
"T": tsval(tm),
"G": geoval(ll),
"L": arrayval(intval(6)),
"M": mapval(map[string]*pb.Value{"a": intval(7)}),
"P": intval(8),
})
)
func TestToProtoValue(t *testing.T) {
*p = 8
for _, test := range []struct {
in interface{}
want *pb.Value
}{
{nil, nullValue},
{[]int(nil), nullValue},
{map[string]int(nil), nullValue},
{(*testStruct1)(nil), nullValue},
{(*latlng.LatLng)(nil), nullValue},
{(*DocumentRef)(nil), nullValue},
{true, boolval(true)},
{3, intval(3)},
{uint32(3), intval(3)},
{1.5, floatval(1.5)},
{"str", strval("str")},
{[]byte{1, 2}, bytesval([]byte{1, 2})},
{tm, tsval(tm)},
{ll, geoval(ll)},
{[]int{1, 2}, arrayval(intval(1), intval(2))},
{&[]int{1, 2}, arrayval(intval(1), intval(2))},
{[]int{}, arrayval()},
{map[string]int{"a": 1, "b": 2},
mapval(map[string]*pb.Value{"a": intval(1), "b": intval(2)})},
{map[string]int{}, mapval(map[string]*pb.Value{})},
{p, intval(8)},
{&p, intval(8)},
{map[string]interface{}{"a": 1, "p": p, "s": "str"},
mapval(map[string]*pb.Value{"a": intval(1), "p": intval(8), "s": strval("str")})},
{map[string]fmt.Stringer{"a": tm},
mapval(map[string]*pb.Value{"a": tsval(tm)})},
{testVal1, mapVal1},
{
&DocumentRef{
ID: "d",
Path: "projects/P/databases/D/documents/c/d",
Parent: &CollectionRef{
ID: "c",
parentPath: "projects/P/databases/D",
Path: "projects/P/databases/D/documents/c",
Query: Query{collectionID: "c", parentPath: "projects/P/databases/D"},
},
},
refval("projects/P/databases/D/documents/c/d"),
},
} {
got, err := toProtoValue(reflect.ValueOf(test.in))
if err != nil {
t.Errorf("%v (%T): %v", test.in, test.in, err)
continue
}
if !testEqual(got, test.want) {
t.Errorf("%+v (%T):\ngot\n%+v\nwant\n%+v", test.in, test.in, got, test.want)
}
}
}
type stringy struct{}
func (stringy) String() string { return "stringy" }
func TestToProtoValueErrors(t *testing.T) {
for _, in := range []interface{}{
uint64(0), // a bad fit for int64
map[int]bool{}, // map key type is not string
make(chan int), // can't handle type
map[string]fmt.Stringer{"a": stringy{}}, // only empty interfaces
} {
_, err := toProtoValue(reflect.ValueOf(in))
if err == nil {
t.Errorf("%v: got nil, want error", in)
}
}
}
type testStruct2 struct {
Ignore int `firestore:"-"`
Rename int `firestore:"a"`
OmitEmpty int `firestore:",omitempty"`
OmitEmptyTime time.Time `firestore:",omitempty"`
}
func TestToProtoValueTags(t *testing.T) {
in := &testStruct2{
Ignore: 1,
Rename: 2,
OmitEmpty: 3,
OmitEmptyTime: aTime,
}
got, err := toProtoValue(reflect.ValueOf(in))
if err != nil {
t.Fatal(err)
}
want := mapval(map[string]*pb.Value{
"a": intval(2),
"OmitEmpty": intval(3),
"OmitEmptyTime": tsval(aTime),
})
if !testEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
got, err = toProtoValue(reflect.ValueOf(testStruct2{}))
if err != nil {
t.Fatal(err)
}
want = mapval(map[string]*pb.Value{"a": intval(0)})
if !testEqual(got, want) {
t.Errorf("got\n%+v\nwant\n%+v", got, want)
}
}
func TestToProtoValueEmbedded(t *testing.T) {
// Embedded time.Time or LatLng should behave like non-embedded.
type embed struct {
time.Time
*latlng.LatLng
}
got, err := toProtoValue(reflect.ValueOf(embed{tm, ll}))
if err != nil {
t.Fatal(err)
}
want := mapval(map[string]*pb.Value{
"Time": tsval(tm),
"LatLng": geoval(ll),
})
if !testEqual(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
}
func TestIsEmpty(t *testing.T) {
for _, e := range []interface{}{int(0), float32(0), false, "", []int{}, []int(nil), (*int)(nil)} {
if !isEmptyValue(reflect.ValueOf(e)) {
t.Errorf("%v (%T): want true, got false", e, e)
}
}
i := 3
for _, n := range []interface{}{int(1), float32(1), true, "x", []int{1}, &i} {
if isEmptyValue(reflect.ValueOf(n)) {
t.Errorf("%v (%T): want false, got true", n, n)
}
}
}

279
vendor/cloud.google.com/go/firestore/transaction.go generated vendored Normal file
View File

@@ -0,0 +1,279 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
gax "github.com/googleapis/gax-go"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// Transaction represents a Firestore transaction.
type Transaction struct {
c *Client
ctx context.Context
id []byte
writes []*pb.Write
maxAttempts int
readOnly bool
readAfterWrite bool
}
// A TransactionOption is an option passed to Client.Transaction.
type TransactionOption interface {
config(t *Transaction)
}
// MaxAttempts is a TransactionOption that configures the maximum number of times to
// try a transaction. In defaults to DefaultTransactionMaxAttempts.
func MaxAttempts(n int) maxAttempts { return maxAttempts(n) }
type maxAttempts int
func (m maxAttempts) config(t *Transaction) { t.maxAttempts = int(m) }
// DefaultTransactionMaxAttempts is the default number of times to attempt a transaction.
const DefaultTransactionMaxAttempts = 5
// ReadOnly is a TransactionOption that makes the transaction read-only. Read-only
// transactions cannot issue write operations, but are more efficient.
var ReadOnly = ro{}
type ro struct{}
func (ro) config(t *Transaction) { t.readOnly = true }
var (
// ErrConcurrentTransaction is returned when a transaction is rolled back due
// to a conflict with a concurrent transaction.
ErrConcurrentTransaction = errors.New("firestore: concurrent transaction")
// Defined here for testing.
errReadAfterWrite = errors.New("firestore: read after write in transaction")
errWriteReadOnly = errors.New("firestore: write in read-only transaction")
errNonTransactionalOp = errors.New("firestore: non-transactional operation inside a transaction")
errNestedTransaction = errors.New("firestore: nested transaction")
)
type transactionInProgressKey struct{}
func checkTransaction(ctx context.Context) error {
if ctx.Value(transactionInProgressKey{}) != nil {
return errNonTransactionalOp
}
return nil
}
// RunTransaction runs f in a transaction. f should use the transaction it is given
// for all Firestore operations. For any operation requiring a context, f should use
// the context it is passed, not the first argument to RunTransaction.
//
// f must not call Commit or Rollback on the provided Transaction.
//
// If f returns nil, RunTransaction commits the transaction. If the commit fails due
// to a conflicting transaction, RunTransaction retries f. It gives up and returns
// ErrConcurrentTransaction after a number of attempts that can be configured with
// the MaxAttempts option. If the commit succeeds, RunTransaction returns a nil error.
//
// If f returns non-nil, then the transaction will be rolled back and
// this method will return the same error. The function f is not retried.
//
// Note that when f returns, the transaction is not committed. Calling code
// must not assume that any of f's changes have been committed until
// RunTransaction returns nil.
//
// Since f may be called more than once, f should usually be idempotent that is, it
// should have the same result when called multiple times.
func (c *Client) RunTransaction(ctx context.Context, f func(context.Context, *Transaction) error, opts ...TransactionOption) error {
if ctx.Value(transactionInProgressKey{}) != nil {
return errNestedTransaction
}
db := c.path()
t := &Transaction{
c: c,
ctx: withResourceHeader(ctx, db),
maxAttempts: DefaultTransactionMaxAttempts,
}
for _, opt := range opts {
opt.config(t)
}
var txOpts *pb.TransactionOptions
if t.readOnly {
txOpts = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{&pb.TransactionOptions_ReadOnly{}},
}
}
var backoff gax.Backoff
// TODO(jba): use other than the standard backoff parameters?
// TODO(jba): get backoff time from gRPC trailer metadata? See extractRetryDelay in https://code.googlesource.com/gocloud/+/master/spanner/retry.go.
var err error
for i := 0; i < t.maxAttempts; i++ {
var res *pb.BeginTransactionResponse
res, err = t.c.c.BeginTransaction(t.ctx, &pb.BeginTransactionRequest{
Database: db,
Options: txOpts,
})
if err != nil {
return err
}
t.id = res.Transaction
err = f(context.WithValue(ctx, transactionInProgressKey{}, 1), t)
// Read after write can only be checked client-side, so we make sure to check
// even if the user does not.
if err == nil && t.readAfterWrite {
err = errReadAfterWrite
}
if err != nil {
t.rollback()
// Prefer f's returned error to rollback error.
return err
}
_, err = t.c.c.Commit(t.ctx, &pb.CommitRequest{
Database: t.c.path(),
Writes: t.writes,
Transaction: t.id,
})
// If a read-write transaction returns Aborted, retry.
// On success or other failures, return here.
if t.readOnly || grpc.Code(err) != codes.Aborted {
// According to the Firestore team, we should not roll back here
// if err != nil. But spanner does.
// See https://code.googlesource.com/gocloud/+/master/spanner/transaction.go#740.
return err
}
if txOpts == nil {
// txOpts can only be nil if is the first retry of a read-write transaction.
// (It is only set here and in the body of "if t.readOnly" above.)
// Mention the transaction ID in BeginTransaction so the service
// knows it is a retry.
txOpts = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{
&pb.TransactionOptions_ReadWrite{RetryTransaction: t.id},
},
}
}
// Use exponential backoff to avoid contention with other running
// transactions.
if cerr := gax.Sleep(ctx, backoff.Pause()); cerr != nil {
err = cerr
break
}
}
// If we run out of retries, return the last error we saw (which should
// be the Aborted from Commit, or a context error).
if err != nil {
t.rollback()
}
return err
}
func (t *Transaction) rollback() {
_ = t.c.c.Rollback(t.ctx, &pb.RollbackRequest{
Database: t.c.path(),
Transaction: t.id,
})
// Ignore the rollback error.
// TODO(jba): Log it?
// Note: Rollback is idempotent so it will be retried by the gapic layer.
}
// Get gets the document in the context of the transaction.
func (t *Transaction) Get(dr *DocumentRef) (*DocumentSnapshot, error) {
if len(t.writes) > 0 {
t.readAfterWrite = true
return nil, errReadAfterWrite
}
docProto, err := t.c.c.GetDocument(t.ctx, &pb.GetDocumentRequest{
Name: dr.Path,
ConsistencySelector: &pb.GetDocumentRequest_Transaction{t.id},
})
if err != nil {
return nil, err
}
return newDocumentSnapshot(dr, docProto, t.c)
}
// A Queryer is a Query or a CollectionRef. CollectionRefs act as queries whose
// results are all the documents in the collection.
type Queryer interface {
query() *Query
}
// Documents returns a DocumentIterator based on given Query or CollectionRef. The
// results will be in the context of the transaction.
func (t *Transaction) Documents(q Queryer) *DocumentIterator {
if len(t.writes) > 0 {
t.readAfterWrite = true
return &DocumentIterator{err: errReadAfterWrite}
}
return &DocumentIterator{
ctx: t.ctx,
q: q.query(),
tid: t.id,
}
}
// Create adds a Create operation to the Transaction.
// See DocumentRef.Create for details.
func (t *Transaction) Create(dr *DocumentRef, data interface{}) error {
return t.addWrites(dr.newReplaceWrites(data, nil, Exists(false)))
}
// Set adds a Set operation to the Transaction.
// See DocumentRef.Set for details.
func (t *Transaction) Set(dr *DocumentRef, data interface{}, opts ...SetOption) error {
return t.addWrites(dr.newReplaceWrites(data, opts, nil))
}
// Delete adds a Delete operation to the Transaction.
// See DocumentRef.Delete for details.
func (t *Transaction) Delete(dr *DocumentRef, opts ...Precondition) error {
return t.addWrites(dr.newDeleteWrites(opts))
}
// UpdateMap adds a new Update operation to the Transaction.
// See DocumentRef.UpdateMap for details.
func (t *Transaction) UpdateMap(dr *DocumentRef, data map[string]interface{}, opts ...Precondition) error {
return t.addWrites(dr.newUpdateMapWrites(data, opts))
}
// UpdateStruct adds a new Update operation to the Transaction.
// See DocumentRef.UpdateStruct for details.
func (t *Transaction) UpdateStruct(dr *DocumentRef, fieldPaths []string, data interface{}, opts ...Precondition) error {
return t.addWrites(dr.newUpdateStructWrites(fieldPaths, data, opts))
}
// UpdatePaths adds a new Update operation to the Transaction.
// See DocumentRef.UpdatePaths for details.
func (t *Transaction) UpdatePaths(dr *DocumentRef, data []FieldPathUpdate, opts ...Precondition) error {
return t.addWrites(dr.newUpdatePathWrites(data, opts))
}
func (t *Transaction) addWrites(ws []*pb.Write, err error) error {
if t.readOnly {
return errWriteReadOnly
}
if err != nil {
return err
}
t.writes = append(t.writes, ws...)
return nil
}

View File

@@ -0,0 +1,346 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"golang.org/x/net/context"
"testing"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/api/iterator"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
func TestRunTransaction(t *testing.T) {
ctx := context.Background()
const db = "projects/projectID/databases/(default)"
tid := []byte{1}
c, srv := newMock(t)
beginReq := &pb.BeginTransactionRequest{Database: db}
beginRes := &pb.BeginTransactionResponse{Transaction: tid}
commitReq := &pb.CommitRequest{Database: db, Transaction: tid}
// Empty transaction.
srv.addRPC(beginReq, beginRes)
srv.addRPC(commitReq, &pb.CommitResponse{CommitTime: aTimestamp})
err := c.RunTransaction(ctx, func(context.Context, *Transaction) error { return nil })
if err != nil {
t.Fatal(err)
}
// Transaction with read and write.
srv.reset()
srv.addRPC(beginReq, beginRes)
aDoc := &pb.Document{
Name: db + "/documents/C/a",
CreateTime: aTimestamp,
UpdateTime: aTimestamp2,
Fields: map[string]*pb.Value{"count": intval(1)},
}
srv.addRPC(
&pb.GetDocumentRequest{
Name: db + "/documents/C/a",
ConsistencySelector: &pb.GetDocumentRequest_Transaction{tid},
},
aDoc,
)
aDoc2 := &pb.Document{
Name: aDoc.Name,
Fields: map[string]*pb.Value{"count": intval(2)},
}
srv.addRPC(
&pb.CommitRequest{
Database: db,
Transaction: tid,
Writes: []*pb.Write{{
Operation: &pb.Write_Update{aDoc2},
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"count"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
}},
},
&pb.CommitResponse{CommitTime: aTimestamp3},
)
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
docref := c.Collection("C").Doc("a")
doc, err := tx.Get(docref)
if err != nil {
return err
}
count, err := doc.DataAt("count")
if err != nil {
return err
}
tx.UpdateMap(docref, map[string]interface{}{"count": count.(int64) + 1})
return nil
})
if err != nil {
t.Fatal(err)
}
// Query
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(
&pb.RunQueryRequest{
Parent: db,
QueryType: &pb.RunQueryRequest_StructuredQuery{
&pb.StructuredQuery{
From: []*pb.StructuredQuery_CollectionSelector{{CollectionId: "C"}},
},
},
ConsistencySelector: &pb.RunQueryRequest_Transaction{tid},
},
[]interface{}{},
)
srv.addRPC(commitReq, &pb.CommitResponse{CommitTime: aTimestamp3})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
it := tx.Documents(c.Collection("C"))
_, err := it.Next()
if err != iterator.Done {
return err
}
return nil
})
if err != nil {
t.Fatal(err)
}
// Retry entire transaction.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(commitReq, grpc.Errorf(codes.Aborted, ""))
srv.addRPC(
&pb.BeginTransactionRequest{
Database: db,
Options: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{
&pb.TransactionOptions_ReadWrite{tid},
},
},
},
beginRes,
)
srv.addRPC(commitReq, &pb.CommitResponse{CommitTime: aTimestamp})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error { return nil })
if err != nil {
t.Fatal(err)
}
}
func TestTransactionErrors(t *testing.T) {
ctx := context.Background()
const db = "projects/projectID/databases/(default)"
c, srv := newMock(t)
var (
tid = []byte{1}
internalErr = grpc.Errorf(codes.Internal, "so sad")
beginReq = &pb.BeginTransactionRequest{
Database: db,
}
beginRes = &pb.BeginTransactionResponse{Transaction: tid}
getReq = &pb.GetDocumentRequest{
Name: db + "/documents/C/a",
ConsistencySelector: &pb.GetDocumentRequest_Transaction{tid},
}
rollbackReq = &pb.RollbackRequest{Database: db, Transaction: tid}
commitReq = &pb.CommitRequest{Database: db, Transaction: tid}
)
// BeginTransaction has a permanent error.
srv.addRPC(beginReq, internalErr)
err := c.RunTransaction(ctx, func(context.Context, *Transaction) error { return nil })
if grpc.Code(err) != codes.Internal {
t.Errorf("got <%v>, want Internal", err)
}
// Get has a permanent error.
get := func(_ context.Context, tx *Transaction) error {
_, err := tx.Get(c.Doc("C/a"))
return err
}
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(getReq, internalErr)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, get)
if grpc.Code(err) != codes.Internal {
t.Errorf("got <%v>, want Internal", err)
}
// Get has a permanent error, but the rollback fails. We still
// return Get's error.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(getReq, internalErr)
srv.addRPC(rollbackReq, grpc.Errorf(codes.FailedPrecondition, ""))
err = c.RunTransaction(ctx, get)
if grpc.Code(err) != codes.Internal {
t.Errorf("got <%v>, want Internal", err)
}
// Commit has a permanent error.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(getReq, &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/a",
CreateTime: aTimestamp,
UpdateTime: aTimestamp2,
})
srv.addRPC(commitReq, internalErr)
err = c.RunTransaction(ctx, get)
if grpc.Code(err) != codes.Internal {
t.Errorf("got <%v>, want Internal", err)
}
// Read after write.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
tx.Delete(c.Doc("C/a"))
if _, err := tx.Get(c.Doc("C/a")); err != nil {
return err
}
return nil
})
if err != errReadAfterWrite {
t.Errorf("got <%v>, want <%v>", err, errReadAfterWrite)
}
// Read after write, with query.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
tx.Delete(c.Doc("C/a"))
it := tx.Documents(c.Collection("C").Select("x"))
if _, err := it.Next(); err != iterator.Done {
return err
}
return nil
})
if err != errReadAfterWrite {
t.Errorf("got <%v>, want <%v>", err, errReadAfterWrite)
}
// Read after write fails even if the user ignores the read's error.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
tx.Delete(c.Doc("C/a"))
tx.Get(c.Doc("C/a"))
return nil
})
if err != errReadAfterWrite {
t.Errorf("got <%v>, want <%v>", err, errReadAfterWrite)
}
// Write in read-only transaction.
srv.reset()
srv.addRPC(
&pb.BeginTransactionRequest{
Database: db,
Options: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{&pb.TransactionOptions_ReadOnly{}},
},
},
beginRes,
)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
return tx.Delete(c.Doc("C/a"))
}, ReadOnly)
if err != errWriteReadOnly {
t.Errorf("got <%v>, want <%v>", err, errWriteReadOnly)
}
// Too many retries.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(commitReq, grpc.Errorf(codes.Aborted, ""))
srv.addRPC(
&pb.BeginTransactionRequest{
Database: db,
Options: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{
&pb.TransactionOptions_ReadWrite{tid},
},
},
},
beginRes,
)
srv.addRPC(commitReq, grpc.Errorf(codes.Aborted, ""))
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(context.Context, *Transaction) error { return nil },
MaxAttempts(2))
if grpc.Code(err) != codes.Aborted {
t.Errorf("got <%v>, want Aborted", err)
}
// Nested transaction.
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(ctx context.Context, tx *Transaction) error {
return c.RunTransaction(ctx, func(context.Context, *Transaction) error { return nil })
})
if got, want := err, errNestedTransaction; got != want {
t.Errorf("got <%v>, want <%V>", got, want)
}
// Non-transactional operation.
dr := c.Doc("C/d")
for i, op := range []func(ctx context.Context) error{
func(ctx context.Context) error { _, err := c.GetAll(ctx, []*DocumentRef{dr}); return err },
func(ctx context.Context) error { _, _, err := c.Collection("C").Add(ctx, testData); return err },
func(ctx context.Context) error { _, err := dr.Get(ctx); return err },
func(ctx context.Context) error { _, err := dr.Create(ctx, testData); return err },
func(ctx context.Context) error { _, err := dr.Set(ctx, testData); return err },
func(ctx context.Context) error { _, err := dr.Delete(ctx); return err },
func(ctx context.Context) error { _, err := dr.UpdateMap(ctx, testData); return err },
func(ctx context.Context) error {
_, err := dr.UpdateStruct(ctx, []string{"x"}, struct{}{})
return err
},
func(ctx context.Context) error {
_, err := dr.UpdatePaths(ctx, []FieldPathUpdate{{Path: []string{"*"}, Value: 1}})
return err
},
func(ctx context.Context) error { it := c.Collections(ctx); _, err := it.Next(); return err },
func(ctx context.Context) error { it := dr.Collections(ctx); _, err := it.Next(); return err },
func(ctx context.Context) error { _, err := c.Batch().Commit(ctx); return err },
func(ctx context.Context) error {
it := c.Collection("C").Documents(ctx)
_, err := it.Next()
return err
},
} {
srv.reset()
srv.addRPC(beginReq, beginRes)
srv.addRPC(rollbackReq, &empty.Empty{})
err = c.RunTransaction(ctx, func(ctx context.Context, _ *Transaction) error {
return op(ctx)
})
if got, want := err, errNonTransactionalOp; got != want {
t.Errorf("#%d: got <%v>, want <%v>", i, got, want)
}
}
}

147
vendor/cloud.google.com/go/firestore/util_test.go generated vendored Normal file
View File

@@ -0,0 +1,147 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"fmt"
"reflect"
"testing"
"time"
"golang.org/x/net/context"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
tspb "github.com/golang/protobuf/ptypes/timestamp"
"google.golang.org/api/option"
"google.golang.org/genproto/googleapis/type/latlng"
"google.golang.org/grpc"
)
var (
aTime = time.Date(2017, 1, 26, 0, 0, 0, 0, time.UTC)
aTime2 = time.Date(2017, 2, 5, 0, 0, 0, 0, time.UTC)
aTime3 = time.Date(2017, 3, 20, 0, 0, 0, 0, time.UTC)
aTimestamp = mustTimestampProto(aTime)
aTimestamp2 = mustTimestampProto(aTime2)
aTimestamp3 = mustTimestampProto(aTime3)
)
func mustTimestampProto(t time.Time) *tspb.Timestamp {
ts, err := ptypes.TimestampProto(t)
if err != nil {
panic(err)
}
return ts
}
// testEqual implements equality for Firestore tests.
func testEqual(a, b interface{}) bool {
switch a := a.(type) {
case time.Time:
return a.Equal(b.(time.Time))
case proto.Message:
return proto.Equal(a, b.(proto.Message))
case *DocumentSnapshot:
return a.equal(b.(*DocumentSnapshot))
case *DocumentRef:
return a.equal(b.(*DocumentRef))
case *CollectionRef:
return a.equal(b.(*CollectionRef))
default:
return reflect.DeepEqual(a, b)
}
}
func TestTestEqual(t *testing.T) {
for _, test := range []struct {
a, b interface{}
want bool
}{
{nil, nil, true},
{([]int)(nil), nil, false},
{nil, ([]int)(nil), false},
{([]int)(nil), ([]int)(nil), true},
} {
if got := testEqual(test.a, test.b); got != test.want {
t.Errorf("testEqual(%#v, %#v) == %t, want %t", test.a, test.b, got, test.want)
}
}
}
func newMock(t *testing.T) (*Client, *mockServer) {
srv, err := newMockServer()
if err != nil {
t.Fatal(err)
}
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
t.Fatal(err)
}
client, err := NewClient(context.Background(), "projectID", option.WithGRPCConn(conn))
if err != nil {
t.Fatal(err)
}
return client, srv
}
func intval(i int) *pb.Value {
return &pb.Value{&pb.Value_IntegerValue{int64(i)}}
}
func boolval(b bool) *pb.Value {
return &pb.Value{&pb.Value_BooleanValue{b}}
}
func floatval(f float64) *pb.Value {
return &pb.Value{&pb.Value_DoubleValue{f}}
}
func strval(s string) *pb.Value {
return &pb.Value{&pb.Value_StringValue{s}}
}
func bytesval(b []byte) *pb.Value {
return &pb.Value{&pb.Value_BytesValue{b}}
}
func tsval(t time.Time) *pb.Value {
ts, err := ptypes.TimestampProto(t)
if err != nil {
panic(fmt.Sprintf("bad time %s in test: %v", t, err))
}
return &pb.Value{&pb.Value_TimestampValue{ts}}
}
func geoval(ll *latlng.LatLng) *pb.Value {
return &pb.Value{&pb.Value_GeoPointValue{ll}}
}
func arrayval(s ...*pb.Value) *pb.Value {
if s == nil {
s = []*pb.Value{}
}
return &pb.Value{&pb.Value_ArrayValue{&pb.ArrayValue{s}}}
}
func mapval(m map[string]*pb.Value) *pb.Value {
return &pb.Value{&pb.Value_MapValue{&pb.MapValue{m}}}
}
func refval(path string) *pb.Value {
return &pb.Value{&pb.Value_ReferenceValue{path}}
}

113
vendor/cloud.google.com/go/firestore/writebatch.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"errors"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
)
// A WriteBatch holds multiple database updates. Build a batch with the Create, Set,
// Update and Delete methods, then run it with the Commit method. Errors in Create,
// Set, Update or Delete are recorded instead of being returned immediately. The
// first such error is returned by Commit.
type WriteBatch struct {
c *Client
err error
writes []*pb.Write
}
func (b *WriteBatch) add(ws []*pb.Write, err error) *WriteBatch {
if b.err != nil {
return b
}
if err != nil {
b.err = err
return b
}
b.writes = append(b.writes, ws...)
return b
}
// Create adds a Create operation to the batch.
// See DocumentRef.Create for details.
func (b *WriteBatch) Create(dr *DocumentRef, data interface{}) *WriteBatch {
return b.add(dr.newReplaceWrites(data, nil, Exists(false)))
}
// Set adds a Set operation to the batch.
// See DocumentRef.Set for details.
func (b *WriteBatch) Set(dr *DocumentRef, data interface{}, opts ...SetOption) *WriteBatch {
return b.add(dr.newReplaceWrites(data, opts, nil))
}
// Delete adds a Delete operation to the batch.
// See DocumentRef.Delete for details.
func (b *WriteBatch) Delete(dr *DocumentRef, opts ...Precondition) *WriteBatch {
return b.add(dr.newDeleteWrites(opts))
}
// UpdateMap adds an UpdateMap operation to the batch.
// See DocumentRef.UpdateMap for details.
func (b *WriteBatch) UpdateMap(dr *DocumentRef, data map[string]interface{}, opts ...Precondition) *WriteBatch {
return b.add(dr.newUpdateMapWrites(data, opts))
}
// UpdateStruct adds an UpdateStruct operation to the batch.
// See DocumentRef.UpdateStruct for details.
func (b *WriteBatch) UpdateStruct(dr *DocumentRef, fieldPaths []string, data interface{}, opts ...Precondition) *WriteBatch {
return b.add(dr.newUpdateStructWrites(fieldPaths, data, opts))
}
// UpdatePaths adds an UpdatePaths operation to the batch.
// See DocumentRef.UpdatePaths for details.
func (b *WriteBatch) UpdatePaths(dr *DocumentRef, data []FieldPathUpdate, opts ...Precondition) *WriteBatch {
return b.add(dr.newUpdatePathWrites(data, opts))
}
// Commit applies all the writes in the batch to the database atomically. Commit
// returns an error if there are no writes in the batch, if any errors occurred in
// constructing the writes, or if the Commmit operation fails.
func (b *WriteBatch) Commit(ctx context.Context) ([]*WriteResult, error) {
if err := checkTransaction(ctx); err != nil {
return nil, err
}
if b.err != nil {
return nil, b.err
}
if len(b.writes) == 0 {
return nil, errors.New("firestore: cannot commit empty WriteBatch")
}
db := b.c.path()
res, err := b.c.c.Commit(withResourceHeader(ctx, db), &pb.CommitRequest{
Database: db,
Writes: b.writes,
})
if err != nil {
return nil, err
}
var wrs []*WriteResult
for _, pwr := range res.WriteResults {
wr, err := writeResultFromProto(pwr)
if err != nil {
return nil, err
}
wrs = append(wrs, wr)
}
return wrs, nil
}

145
vendor/cloud.google.com/go/firestore/writebatch_test.go generated vendored Normal file
View File

@@ -0,0 +1,145 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package firestore
import (
"testing"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
)
func TestWriteBatch(t *testing.T) {
type update struct{ A int }
c, srv := newMock(t)
docPrefix := c.Collection("C").Path + "/"
srv.addRPC(
&pb.CommitRequest{
Database: c.path(),
Writes: []*pb.Write{
{ // Create
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: docPrefix + "a",
Fields: testFields,
},
},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{false},
},
},
{ // Set
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: docPrefix + "b",
Fields: testFields,
},
},
},
{ // Delete
Operation: &pb.Write_Delete{
Delete: docPrefix + "c",
},
},
{ // UpdateMap
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: docPrefix + "d",
Fields: testFields,
},
},
UpdateMask: &pb.DocumentMask{[]string{"a"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
},
{ // UpdateStruct
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: docPrefix + "e",
Fields: map[string]*pb.Value{"A": intval(3)},
},
},
UpdateMask: &pb.DocumentMask{[]string{"A"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
},
{ // UpdatePaths
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: docPrefix + "f",
Fields: map[string]*pb.Value{"*": intval(3)},
},
},
UpdateMask: &pb.DocumentMask{[]string{"`*`"}},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{true},
},
},
},
},
&pb.CommitResponse{
WriteResults: []*pb.WriteResult{
{UpdateTime: aTimestamp},
{UpdateTime: aTimestamp2},
{UpdateTime: aTimestamp3},
},
},
)
gotWRs, err := c.Batch().
Create(c.Doc("C/a"), testData).
Set(c.Doc("C/b"), testData).
Delete(c.Doc("C/c")).
UpdateMap(c.Doc("C/d"), testData).
UpdateStruct(c.Doc("C/e"), []string{"A"}, update{A: 3}).
UpdatePaths(c.Doc("C/f"), []FieldPathUpdate{{Path: []string{"*"}, Value: 3}}).
Commit(context.Background())
if err != nil {
t.Fatal(err)
}
wantWRs := []*WriteResult{{aTime}, {aTime2}, {aTime3}}
if !testEqual(gotWRs, wantWRs) {
t.Errorf("got %+v\nwant %+v", gotWRs, wantWRs)
}
}
func TestWriteBatchErrors(t *testing.T) {
ctx := context.Background()
c, _ := newMock(t)
for _, test := range []struct {
desc string
batch *WriteBatch
}{
{
"empty batch",
c.Batch(),
},
{
"bad doc reference",
c.Batch().Create(c.Doc("a"), testData),
},
{
"bad data",
c.Batch().Create(c.Doc("a/b"), 3),
},
} {
if _, err := test.batch.Commit(ctx); err == nil {
t.Errorf("%s: got nil, want error", test.desc)
}
}
}