1
0
mirror of https://github.com/kubernetes-sigs/descheduler.git synced 2026-01-26 13:29:11 +01:00

bump(*): kubernetes release-1.16.0 dependencies

This commit is contained in:
Mike Dame
2019-10-12 11:11:43 -04:00
parent 5af668e89a
commit 1652ba7976
28121 changed files with 3491095 additions and 2280257 deletions

117
vendor/cloud.google.com/go/datastore/client.go generated vendored Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2017 Google LLC
//
// 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 datastore
import (
"context"
"fmt"
"cloud.google.com/go/internal"
"cloud.google.com/go/internal/version"
gax "github.com/googleapis/gax-go/v2"
pb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// datastoreClient is a wrapper for the pb.DatastoreClient that includes gRPC
// metadata to be sent in each request for server-side traffic management.
type datastoreClient struct {
// Embed so we still implement the DatastoreClient interface,
// if the interface adds more methods.
pb.DatastoreClient
c pb.DatastoreClient
md metadata.MD
}
func newDatastoreClient(conn *grpc.ClientConn, projectID string) pb.DatastoreClient {
return &datastoreClient{
c: pb.NewDatastoreClient(conn),
md: metadata.Pairs(
resourcePrefixHeader, "projects/"+projectID,
"x-goog-api-client", fmt.Sprintf("gl-go/%s gccl/%s grpc/", version.Go(), version.Repo)),
}
}
func (dc *datastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (res *pb.LookupResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.Lookup(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (res *pb.RunQueryResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.RunQuery(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (res *pb.BeginTransactionResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.BeginTransaction(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (res *pb.CommitResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.Commit(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (res *pb.RollbackResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.Rollback(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (res *pb.AllocateIdsResponse, err error) {
err = dc.invoke(ctx, func(ctx context.Context) error {
res, err = dc.c.AllocateIds(ctx, in, opts...)
return err
})
return res, err
}
func (dc *datastoreClient) invoke(ctx context.Context, f func(ctx context.Context) error) error {
ctx = metadata.NewOutgoingContext(ctx, dc.md)
return internal.Retry(ctx, gax.Backoff{}, func() (stop bool, err error) {
err = f(ctx)
return !shouldRetry(err), err
})
}
func shouldRetry(err error) bool {
if err == nil {
return false
}
s, ok := status.FromError(err)
if !ok {
return false
}
// See https://cloud.google.com/datastore/docs/concepts/errors.
return s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,19 +15,19 @@
package datastore
import (
"context"
"errors"
"fmt"
"log"
"os"
"reflect"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/option"
"google.golang.org/api/transport"
gtransport "google.golang.org/api/transport/grpc"
pb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
@@ -38,73 +38,43 @@ const (
// ScopeDatastore grants permissions to view and/or manage datastore entities
const ScopeDatastore = "https://www.googleapis.com/auth/datastore"
// protoClient is an interface for *transport.ProtoClient to support injecting
// fake clients in tests.
type protoClient interface {
Call(context.Context, string, proto.Message, proto.Message) error
}
// DetectProjectID is a sentinel value that instructs NewClient to detect the
// project ID. It is given in place of the projectID argument. NewClient will
// use the project ID from the given credentials or the default credentials
// (https://developers.google.com/accounts/docs/application-default-credentials)
// if no credentials were provided. When providing credentials, not all
// options will allow NewClient to extract the project ID. Specifically a JWT
// does not have the project ID encoded.
const DetectProjectID = "*detect-project-id*"
// datastoreClient is a wrapper for the pb.DatastoreClient that includes gRPC
// metadata to be sent in each request for server-side traffic management.
type datastoreClient struct {
c pb.DatastoreClient
md metadata.MD
}
func newDatastoreClient(conn *grpc.ClientConn, projectID string) pb.DatastoreClient {
return &datastoreClient{
c: pb.NewDatastoreClient(conn),
md: metadata.Pairs(resourcePrefixHeader, "projects/"+projectID),
}
}
func (dc *datastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (*pb.LookupResponse, error) {
return dc.c.Lookup(metadata.NewContext(ctx, dc.md), in, opts...)
}
func (dc *datastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (*pb.RunQueryResponse, error) {
return dc.c.RunQuery(metadata.NewContext(ctx, dc.md), in, opts...)
}
func (dc *datastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (*pb.BeginTransactionResponse, error) {
return dc.c.BeginTransaction(metadata.NewContext(ctx, dc.md), in, opts...)
}
func (dc *datastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (*pb.CommitResponse, error) {
return dc.c.Commit(metadata.NewContext(ctx, dc.md), in, opts...)
}
func (dc *datastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (*pb.RollbackResponse, error) {
return dc.c.Rollback(metadata.NewContext(ctx, dc.md), in, opts...)
}
func (dc *datastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (*pb.AllocateIdsResponse, error) {
return dc.c.AllocateIds(metadata.NewContext(ctx, dc.md), in, opts...)
}
// resourcePrefixHeader is the name of the metadata header used to indicate
// the resource being operated on.
const resourcePrefixHeader = "google-cloud-resource-prefix"
// Client is a client for reading and writing data in a datastore dataset.
type Client struct {
conn *grpc.ClientConn
client pb.DatastoreClient
endpoint string
dataset string // Called dataset by the datastore API, synonym for project ID.
conn *grpc.ClientConn
client pb.DatastoreClient
dataset string // Called dataset by the datastore API, synonym for project ID.
}
// NewClient creates a new Client for a given dataset.
// If the project ID is empty, it is derived from the DATASTORE_PROJECT_ID environment variable.
// If the DATASTORE_EMULATOR_HOST environment variable is set, client will use its value
// to connect to a locally-running datastore emulator.
// NewClient creates a new Client for a given dataset. If the project ID is
// empty, it is derived from the DATASTORE_PROJECT_ID environment variable.
// If the DATASTORE_EMULATOR_HOST environment variable is set, client will use
// its value to connect to a locally-running datastore emulator.
// DetectProjectID can be passed as the projectID argument to instruct
// NewClient to detect the project ID from the credentials.
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
var o []option.ClientOption
// Environment variables for gcd emulator:
// https://cloud.google.com/datastore/docs/tools/datastore-emulator
// If the emulator is available, dial it directly (and don't pass any credentials).
// If the emulator is available, dial it without passing any credentials.
if addr := os.Getenv("DATASTORE_EMULATOR_HOST"); addr != "" {
conn, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
return nil, fmt.Errorf("grpc.Dial: %v", err)
o = []option.ClientOption{
option.WithEndpoint(addr),
option.WithoutAuthentication(),
option.WithGRPCDialOption(grpc.WithInsecure()),
}
o = []option.ClientOption{option.WithGRPCConn(conn)}
} else {
o = []option.ClientOption{
option.WithEndpoint(prodAddr),
@@ -122,11 +92,26 @@ func NewClient(ctx context.Context, projectID string, opts ...option.ClientOptio
if projectID == "" {
projectID = os.Getenv("DATASTORE_PROJECT_ID")
}
o = append(o, opts...)
if projectID == DetectProjectID {
creds, err := transport.Creds(ctx, o...)
if err != nil {
return nil, fmt.Errorf("fetching creds: %v", err)
}
if creds.ProjectID == "" {
return nil, errors.New("datastore: see the docs on DetectProjectID")
}
projectID = creds.ProjectID
}
if projectID == "" {
return nil, errors.New("datastore: missing project/dataset id")
}
o = append(o, opts...)
conn, err := transport.DialGRPC(ctx, o...)
conn, err := gtransport.Dial(ctx, o...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
@@ -135,7 +120,6 @@ func NewClient(ctx context.Context, projectID string, opts ...option.ClientOptio
client: newDatastoreClient(conn, projectID),
dataset: projectID,
}, nil
}
var (
@@ -158,23 +142,6 @@ const (
multiArgTypeInterface
)
// nsKey is the type of the context.Context key to store the datastore
// namespace.
type nsKey struct{}
// WithNamespace returns a new context that limits the scope its parent
// context with a Datastore namespace.
func WithNamespace(parent context.Context, namespace string) context.Context {
return context.WithValue(parent, nsKey{}, namespace)
}
// ctxNamespace returns the active namespace for a context.
// It defaults to "" if no namespace was specified.
func ctxNamespace(ctx context.Context) string {
v, _ := ctx.Value(nsKey{}).(string)
return v
}
// ErrFieldMismatch is returned when a field is to be loaded into a different
// type than the one it was stored from, or when a field is missing or
// unexported in the destination struct.
@@ -206,25 +173,31 @@ func keyToProto(k *Key) *pb.Key {
return nil
}
// TODO(jbd): Eliminate unrequired allocations.
var path []*pb.Key_PathElement
for {
el := &pb.Key_PathElement{Kind: k.kind}
if k.id != 0 {
el.IdType = &pb.Key_PathElement_Id{k.id}
} else if k.name != "" {
el.IdType = &pb.Key_PathElement_Name{k.name}
el := &pb.Key_PathElement{Kind: k.Kind}
if k.ID != 0 {
el.IdType = &pb.Key_PathElement_Id{Id: k.ID}
} else if k.Name != "" {
el.IdType = &pb.Key_PathElement_Name{Name: k.Name}
}
path = append([]*pb.Key_PathElement{el}, path...)
if k.parent == nil {
path = append(path, el)
if k.Parent == nil {
break
}
k = k.parent
k = k.Parent
}
// The path should be in order [grandparent, parent, child]
// We did it backward above, so reverse back.
for i := 0; i < len(path)/2; i++ {
path[i], path[len(path)-i-1] = path[len(path)-i-1], path[i]
}
key := &pb.Key{Path: path}
if k.namespace != "" {
if k.Namespace != "" {
key.PartitionId = &pb.PartitionId{
NamespaceId: k.namespace,
NamespaceId: k.Namespace,
}
}
return key
@@ -241,11 +214,11 @@ func protoToKey(p *pb.Key) (*Key, error) {
}
for _, el := range p.Path {
key = &Key{
namespace: namespace,
kind: el.Kind,
id: el.GetId(),
name: el.GetName(),
parent: key,
Namespace: namespace,
Kind: el.Kind,
ID: el.GetId(),
Name: el.GetName(),
Parent: key,
}
}
if !key.valid() { // Also detects key == nil.
@@ -310,10 +283,9 @@ func multiValid(key []*Key) error {
// that represents S, I or P.
//
// As a special case, PropertyList is an invalid type for v.
//
// TODO(djd): multiArg is very confusing. Fold this logic into the
// relevant Put/Get methods to make the logic less opaque.
func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
// TODO(djd): multiArg is very confusing. Fold this logic into the
// relevant Put/Get methods to make the logic less opaque.
if v.Kind() != reflect.Slice {
return multiArgTypeInvalid, nil
}
@@ -339,8 +311,8 @@ func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
}
// Close closes the Client.
func (c *Client) Close() {
c.conn.Close()
func (c *Client) Close() error {
return c.conn.Close()
}
// Get loads the entity stored for key into dst, which must be a struct pointer
@@ -355,11 +327,14 @@ func (c *Client) Close() {
// type than the one it was stored from, or when a field is missing or
// unexported in the destination struct. ErrFieldMismatch is only returned if
// dst is a struct pointer.
func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) error {
func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Get")
defer func() { trace.EndSpan(ctx, err) }()
if dst == nil { // get catches nil interfaces; we need to catch nil ptr here
return ErrInvalidEntityType
}
err := c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
err = c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
if me, ok := err.(MultiError); ok {
return me[0]
}
@@ -376,7 +351,12 @@ func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) error {
// As a special case, PropertyList is an invalid type for dst, even though a
// PropertyList is a slice of structs. It is treated as invalid to avoid being
// mistakenly passed when []PropertyList was intended.
func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) error {
//
// err may be a MultiError. See ExampleMultiError to check it.
func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.GetMulti")
defer func() { trace.EndSpan(ctx, err) }()
return c.get(ctx, keys, dst, nil)
}
@@ -395,17 +375,24 @@ func (c *Client) get(ctx context.Context, keys []*Key, dst interface{}, opts *pb
return nil
}
// Go through keys, validate them, serialize then, and create a dict mapping them to their index
// Go through keys, validate them, serialize then, and create a dict mapping them to their indices.
// Equal keys are deduped.
multiErr, any := make(MultiError, len(keys)), false
keyMap := make(map[string]int)
pbKeys := make([]*pb.Key, len(keys))
keyMap := make(map[string][]int, len(keys))
pbKeys := make([]*pb.Key, 0, len(keys))
for i, k := range keys {
if !k.valid() {
multiErr[i] = ErrInvalidKey
any = true
} else if k.Incomplete() {
multiErr[i] = fmt.Errorf("datastore: can't get the incomplete key: %v", k)
any = true
} else {
keyMap[k.String()] = i
pbKeys[i] = keyToProto(k)
ks := k.String()
if _, ok := keyMap[ks]; !ok {
pbKeys = append(pbKeys, keyToProto(k))
}
keyMap[ks] = append(keyMap[ks], i)
}
}
if any {
@@ -420,49 +407,73 @@ func (c *Client) get(ctx context.Context, keys []*Key, dst interface{}, opts *pb
if err != nil {
return err
}
if len(resp.Deferred) > 0 {
// TODO(jbd): Assess whether we should retry the deferred keys.
return errors.New("datastore: some entities temporarily unavailable")
found := resp.Found
missing := resp.Missing
// Upper bound 100 iterations to prevent infinite loop.
// We choose 100 iterations somewhat logically:
// Max number of Entities you can request from Datastore is 1,000.
// Max size for a Datastore Entity is 1 MiB.
// Max request size is 10 MiB, so we assume max response size is also 10 MiB.
// 1,000 / 10 = 100.
// Note that if ctx has a deadline, the deadline will probably
// be hit before we reach 100 iterations.
for i := 0; len(resp.Deferred) > 0 && i < 100; i++ {
req.Keys = resp.Deferred
resp, err = c.client.Lookup(ctx, req)
if err != nil {
return err
}
found = append(found, resp.Found...)
missing = append(missing, resp.Missing...)
}
if len(keys) != len(resp.Found)+len(resp.Missing) {
return errors.New("datastore: internal error: server returned the wrong number of entities")
}
for _, e := range resp.Found {
filled := 0
for _, e := range found {
k, err := protoToKey(e.Entity.Key)
if err != nil {
return errors.New("datastore: internal error: server returned an invalid key")
}
index := keyMap[k.String()]
elem := v.Index(index)
if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
elem = elem.Addr()
}
if multiArgType == multiArgTypeStructPtr && elem.IsNil() {
elem.Set(reflect.New(elem.Type().Elem()))
}
if err := loadEntity(elem.Interface(), e.Entity); err != nil {
multiErr[index] = err
any = true
filled += len(keyMap[k.String()])
for _, index := range keyMap[k.String()] {
elem := v.Index(index)
if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
elem = elem.Addr()
}
if multiArgType == multiArgTypeStructPtr && elem.IsNil() {
elem.Set(reflect.New(elem.Type().Elem()))
}
if err := loadEntityProto(elem.Interface(), e.Entity); err != nil {
multiErr[index] = err
any = true
}
}
}
for _, e := range resp.Missing {
for _, e := range missing {
k, err := protoToKey(e.Entity.Key)
if err != nil {
return errors.New("datastore: internal error: server returned an invalid key")
}
multiErr[keyMap[k.String()]] = ErrNoSuchEntity
filled += len(keyMap[k.String()])
for _, index := range keyMap[k.String()] {
multiErr[index] = ErrNoSuchEntity
}
any = true
}
if filled != len(keys) {
return errors.New("datastore: internal error: server returned the wrong number of entities")
}
if any {
return multiErr
}
return nil
}
// Put saves the entity src into the datastore with key k. src must be a struct
// pointer or implement PropertyLoadSaver; if a struct pointer then any
// unexported fields of that struct will be skipped. If k is an incomplete key,
// the returned key will be a unique key generated by the datastore.
// Put saves the entity src into the datastore with the given key. src must be
// a struct pointer or implement PropertyLoadSaver; if the struct pointer has
// any unexported fields they will be skipped. If the key is incomplete, the
// returned key will be a unique key generated by the datastore.
func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, error) {
k, err := c.PutMulti(ctx, []*Key{key}, []interface{}{src})
if err != nil {
@@ -477,7 +488,12 @@ func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, erro
// PutMulti is a batch version of Put.
//
// src must satisfy the same conditions as the dst argument to GetMulti.
func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) ([]*Key, error) {
// err may be a MultiError. See ExampleMultiError to check it.
func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) (ret []*Key, err error) {
// TODO(jba): rewrite in terms of Mutate.
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.PutMulti")
defer func() { trace.EndSpan(ctx, err) }()
mutations, err := putMutations(keys, src)
if err != nil {
return nil, err
@@ -495,7 +511,7 @@ func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) ([]
}
// Copy any newly minted keys into the returned keys.
ret := make([]*Key, len(keys))
ret = make([]*Key, len(keys))
for i, key := range keys {
if key.Incomplete() {
// This key is in the mutation results.
@@ -526,6 +542,8 @@ func putMutations(keys []*Key, src interface{}) ([]*pb.Mutation, error) {
return nil, err
}
mutations := make([]*pb.Mutation, 0, len(keys))
multiErr := make(MultiError, len(keys))
hasErr := false
for i, k := range keys {
elem := v.Index(i)
// Two cases where we need to take the address:
@@ -536,16 +554,20 @@ func putMutations(keys []*Key, src interface{}) ([]*pb.Mutation, error) {
}
p, err := saveEntity(k, elem.Interface())
if err != nil {
return nil, fmt.Errorf("datastore: Error while saving %v: %v", k.String(), err)
multiErr[i] = err
hasErr = true
}
var mut *pb.Mutation
if k.Incomplete() {
mut = &pb.Mutation{Operation: &pb.Mutation_Insert{p}}
mut = &pb.Mutation{Operation: &pb.Mutation_Insert{Insert: p}}
} else {
mut = &pb.Mutation{Operation: &pb.Mutation_Upsert{p}}
mut = &pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: p}}
}
mutations = append(mutations, mut)
}
if hasErr {
return nil, multiErr
}
return mutations, nil
}
@@ -559,7 +581,13 @@ func (c *Client) Delete(ctx context.Context, key *Key) error {
}
// DeleteMulti is a batch version of Delete.
func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) error {
//
// err may be a MultiError. See ExampleMultiError to check it.
func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) (err error) {
// TODO(jba): rewrite in terms of Mutate.
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.DeleteMulti")
defer func() { trace.EndSpan(ctx, err) }()
mutations, err := deleteMutations(keys)
if err != nil {
return err
@@ -576,13 +604,67 @@ func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) error {
func deleteMutations(keys []*Key) ([]*pb.Mutation, error) {
mutations := make([]*pb.Mutation, 0, len(keys))
for _, k := range keys {
if k.Incomplete() {
return nil, fmt.Errorf("datastore: can't delete the incomplete key: %v", k)
set := make(map[string]bool, len(keys))
multiErr := make(MultiError, len(keys))
hasErr := false
for i, k := range keys {
if !k.valid() {
multiErr[i] = ErrInvalidKey
hasErr = true
} else if k.Incomplete() {
multiErr[i] = fmt.Errorf("datastore: can't delete the incomplete key: %v", k)
hasErr = true
} else {
ks := k.String()
if !set[ks] {
mutations = append(mutations, &pb.Mutation{
Operation: &pb.Mutation_Delete{Delete: keyToProto(k)},
})
}
set[ks] = true
}
mutations = append(mutations, &pb.Mutation{
Operation: &pb.Mutation_Delete{keyToProto(k)},
})
}
if hasErr {
return nil, multiErr
}
return mutations, nil
}
// Mutate applies one or more mutations atomically.
// It returns the keys of the argument Mutations, in the same order.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
// See ExampleMultiError to check it.
func (c *Client) Mutate(ctx context.Context, muts ...*Mutation) (ret []*Key, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Mutate")
defer func() { trace.EndSpan(ctx, err) }()
pmuts, err := mutationProtos(muts)
if err != nil {
return nil, err
}
req := &pb.CommitRequest{
ProjectId: c.dataset,
Mutations: pmuts,
Mode: pb.CommitRequest_NON_TRANSACTIONAL,
}
resp, err := c.client.Commit(ctx, req)
if err != nil {
return nil, err
}
// Copy any newly minted keys into the returned keys.
ret = make([]*Key, len(muts))
for i, mut := range muts {
if mut.key.Incomplete() {
// This key is in the mutation results.
ret[i], err = protoToKey(resp.MutationResults[i].Key)
if err != nil {
return nil, errors.New("datastore: internal error: server returned an invalid key")
}
} else {
ret[i] = mut.key
}
}
return ret, nil
}

BIN
vendor/cloud.google.com/go/datastore/datastore.replay generated vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All Rights Reserved.
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
/*
Package datastore provides a client for Google Cloud Datastore.
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
connection pooling and similar aspects of this package.
Basic Operations
@@ -30,22 +33,21 @@ to be generated for that entity, with a non-zero IntID.
An entity's contents are a mapping from case-sensitive field names to values.
Valid value types are:
- signed integers (int, int8, int16, int32 and int64),
- bool,
- string,
- float32 and float64,
- []byte (up to 1 megabyte in length),
- any type whose underlying type is one of the above predeclared types,
- *Key,
- GeoPoint,
- time.Time (stored with microsecond precision),
- structs whose fields are all valid value types,
- slices of any of the above.
- Signed integers (int, int8, int16, int32 and int64)
- bool
- string
- float32 and float64
- []byte (up to 1 megabyte in length)
- Any type whose underlying type is one of the above predeclared types
- *Key
- GeoPoint
- time.Time (stored with microsecond precision, retrieved as local time)
- Structs whose fields are all valid value types
- Pointers to structs whose fields are all valid value types
- Slices of any of the above
- Pointers to a signed integer, bool, string, float32, or float64
Slices of structs are valid, as are structs that contain slices. However, if
one struct contains another, then at most one of those can be repeated. This
disqualifies recursively defined struct types: any struct T that (directly or
indirectly) contains a []T.
Slices of structs are valid, as are structs that contain slices.
The Get and Put functions load and save an entity's contents. An entity's
contents are typically represented by a struct pointer.
@@ -66,7 +68,7 @@ Example code:
// Handle error.
}
k := datastore.NewKey(ctx, "Entity", "stringID", 0, nil)
k := datastore.NameKey("Entity", "stringID", nil)
e := new(Entity)
if err := dsClient.Get(ctx, k, e); err != nil {
// Handle error.
@@ -86,6 +88,10 @@ GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and
Delete functions. They take a []*Key instead of a *Key, and may return a
datastore.MultiError when encountering partial failure.
Mutate generalizes PutMulti and DeleteMulti to a sequence of any Datastore mutations.
It takes a series of mutations created with NewInsert, NewUpdate, NewUpsert and
NewDelete and applies them atomically.
Properties
@@ -93,8 +99,8 @@ An entity's contents can be represented by a variety of types. These are
typically struct pointers, but can also be any type that implements the
PropertyLoadSaver interface. If using a struct pointer, you do not have to
explicitly implement the PropertyLoadSaver interface; the datastore will
automatically convert via reflection. If a struct pointer does implement that
interface then those methods will be used in preference to the default
automatically convert via reflection. If a struct pointer does implement
PropertyLoadSaver then those methods will be used in preference to the default
behavior for struct pointers. Struct pointers are more strongly typed and are
easier to use; PropertyLoadSavers are more flexible.
@@ -109,17 +115,33 @@ caller whether this error is fatal, recoverable or ignorable.
By default, for struct pointers, all properties are potentially indexed, and
the property name is the same as the field name (and hence must start with an
upper case letter). Fields may have a `datastore:"name,options"` tag. The tag
name is the property name, which must be one or more valid Go identifiers
joined by ".", but may start with a lower case letter. An empty tag name means
to just use the field name. A "-" tag name means that the datastore will
ignore that field. If options is "noindex" then the field will not be indexed.
If the options is "" then the comma may be omitted. There are no other
recognized options.
upper case letter).
All fields are indexed by default. Strings or byte slices longer than 1500
bytes cannot be indexed; fields used to store long strings and byte slices must
be tagged with "noindex" or they will cause Put operations to fail.
Fields may have a `datastore:"name,options"` tag. The tag name is the
property name, which must be one or more valid Go identifiers joined by ".",
but may start with a lower case letter. An empty tag name means to just use the
field name. A "-" tag name means that the datastore will ignore that field.
The only valid options are "omitempty", "noindex" and "flatten".
If the options include "omitempty" and the value of the field is a zero value,
then the field will be omitted on Save. Zero values are best defined in the
golang spec (https://golang.org/ref/spec#The_zero_value). Struct field values
will never be empty, except for nil pointers.
If options include "noindex" then the field will not be indexed. All fields
are indexed by default. Strings or byte slices longer than 1500 bytes cannot
be indexed; fields used to store long strings and byte slices must be tagged
with "noindex" or they will cause Put operations to fail.
For a nested struct field, the options may also include "flatten". This
indicates that the immediate fields and any nested substruct fields of the
nested struct should be flattened. See below for examples.
To use multiple options together, separate them by a comma.
The order does not matter.
If the options is "" then the comma may be omitted.
Example code:
@@ -139,10 +161,112 @@ Example code:
}
Slice Fields
A field of slice type corresponds to a Datastore array property, except for []byte, which corresponds
to a Datastore blob.
Zero-length slice fields are not saved. Slice fields of length 1 or greater are saved
as Datastore arrays. When a zero-length Datastore array is loaded into a slice field,
the slice field remains unchanged.
If a non-array value is loaded into a slice field, the result will be a slice with
one element, containing the value.
Loading Nulls
Loading a Datastore Null into a basic type (int, float, etc.) results in a zero value.
Loading a Null into a slice of basic type results in a slice of size 1 containing the zero value.
Loading a Null into a pointer field results in nil.
Loading a Null into a field of struct type is an error.
Pointer Fields
A struct field can be a pointer to a signed integer, floating-point number, string or
bool. Putting a non-nil pointer will store its dereferenced value. Putting a nil
pointer will store a Datastore Null property, unless the field is marked omitempty,
in which case no property will be stored.
Loading a Null into a pointer field sets the pointer to nil. Loading any other value
allocates new storage with the value, and sets the field to point to it.
Key Field
If the struct contains a *datastore.Key field tagged with the name "__key__",
its value will be ignored on Put. When reading the Entity back into the Go struct,
the field will be populated with the *datastore.Key value used to query for
the Entity.
Example code:
type MyEntity struct {
A int
K *datastore.Key `datastore:"__key__"`
}
func main() {
ctx := context.Background()
dsClient, err := datastore.NewClient(ctx, "my-project")
if err != nil {
// Handle error.
}
k := datastore.NameKey("Entity", "stringID", nil)
e := MyEntity{A: 12}
if _, err := dsClient.Put(ctx, k, &e); err != nil {
// Handle error.
}
var entities []MyEntity
q := datastore.NewQuery("Entity").Filter("A =", 12).Limit(1)
if _, err := dsClient.GetAll(ctx, q, &entities); err != nil {
// Handle error
}
log.Println(entities[0])
// Prints {12 /Entity,stringID}
}
Structured Properties
If the struct pointed to contains other structs, then the nested or embedded
structs are flattened. For example, given these definitions:
structs are themselves saved as Entity values. For example, given these definitions:
type Inner struct {
W int32
X string
}
type Outer struct {
I Inner
}
then an Outer would have one property, Inner, encoded as an Entity value.
If an outer struct is tagged "noindex" then all of its implicit flattened
fields are effectively "noindex".
If the Inner struct contains a *Key field with the name "__key__", like so:
type Inner struct {
W int32
X string
K *datastore.Key `datastore:"__key__"`
}
type Outer struct {
I Inner
}
then the value of K will be used as the Key for Inner, represented
as an Entity value in datastore.
If any nested struct fields should be flattened, instead of encoded as
Entity values, the nested struct field should be tagged with the "flatten"
option. For example, given the following:
type Inner1 struct {
W int32
@@ -157,28 +281,36 @@ structs are flattened. For example, given these definitions:
Z bool
}
type Inner4 struct {
WW int
}
type Inner5 struct {
X Inner4
}
type Outer struct {
A int16
I []Inner1
J Inner2
Inner3
I []Inner1 `datastore:",flatten"`
J Inner2 `datastore:",flatten"`
K Inner5 `datastore:",flatten"`
Inner3 `datastore:",flatten"`
}
then an Outer's properties would be equivalent to those of:
an Outer's properties would be equivalent to those of:
type OuterEquivalent struct {
A int16
IDotW []int32 `datastore:"I.W"`
IDotX []string `datastore:"I.X"`
JDotY float64 `datastore:"J.Y"`
Z bool
A int16
IDotW []int32 `datastore:"I.W"`
IDotX []string `datastore:"I.X"`
JDotY float64 `datastore:"J.Y"`
KDotXDotWW int `datastore:"K.X.WW"`
Z bool
}
If Outer's embedded Inner3 field was tagged as `datastore:"Foo"` then the
equivalent field would instead be: FooDotZ bool `datastore:"Foo.Z"`.
If an outer struct is tagged "noindex" then all of its implicit flattened
fields are effectively "noindex".
Note that the "flatten" option cannot be used for Entity value fields or
PropertyLoadSaver implementers. The server will reject any dotted field names
for an Entity value.
The PropertyLoadSaver Interface
@@ -211,7 +343,7 @@ Example code:
func (x *CustomPropsExample) Save() ([]datastore.Property, error) {
// Validate the Sum field.
if x.Sum != x.I + x.J {
return errors.New("CustomPropsExample has inconsistent sum")
return nil, errors.New("CustomPropsExample has inconsistent sum")
}
// Save I and J as usual. The code below is equivalent to calling
// "return datastore.SaveStruct(x)", but is done manually for
@@ -225,12 +357,46 @@ Example code:
Name: "J",
Value: int64(x.J),
},
}
}, nil
}
The *PropertyList type implements PropertyLoadSaver, and can therefore hold an
arbitrary entity's contents.
The KeyLoader Interface
If a type implements the PropertyLoadSaver interface, it may
also want to implement the KeyLoader interface.
The KeyLoader interface exists to allow implementations of PropertyLoadSaver
to also load an Entity's Key into the Go type. This type may be a struct
pointer, but it does not have to be. The datastore package will call LoadKey
when getting the entity's contents, after calling Load.
Example code:
type WithKeyExample struct {
I int
Key *datastore.Key
}
func (x *WithKeyExample) LoadKey(k *datastore.Key) error {
x.Key = k
return nil
}
func (x *WithKeyExample) Load(ps []datastore.Property) error {
// Load I as usual.
return datastore.LoadStruct(x, ps)
}
func (x *WithKeyExample) Save() ([]datastore.Property, error) {
// Save I as usual.
return datastore.SaveStruct(x)
}
To load a Key into a struct which does not implement the PropertyLoadSaver
interface, see the "Key Field" section above.
Queries
@@ -267,10 +433,12 @@ Example code:
q := datastore.NewQuery("Widget").
Filter("Price <", 1000).
Order("-Price")
for t := dsClient.Run(ctx, q); ; {
t := client.Run(ctx, q)
for {
var x Widget
key, err := t.Next(&x)
if err == datastore.Done {
if err == iterator.Done {
break
}
if err != nil {
@@ -293,8 +461,8 @@ Example code:
func incCount(ctx context.Context, client *datastore.Client) {
var count int
key := datastore.NewKey(ctx, "Counter", "singleton", 0, nil)
err := dsClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
key := datastore.NameKey("Counter", "singleton", nil)
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
var x Counter
if err := tx.Get(key, &x); err != nil && err != datastore.ErrNoSuchEntity {
return err
@@ -304,7 +472,8 @@ Example code:
return err
}
count = x.Count
}, nil)
return nil
})
if err != nil {
// Handle error.
}
@@ -312,9 +481,17 @@ Example code:
// (RunInTransaction has returned nil).
fmt.Printf("Count=%d\n", count)
}
Pass the ReadOnly option to RunInTransaction if your transaction is used only for Get,
GetMulti or queries. Read-only transactions are more efficient.
Google Cloud Datastore Emulator
This package supports the Cloud Datastore emulator, which is useful for testing and
development. Environment variables are used to indicate that datastore traffic should be
directed to the emulator instead of the production Datastore service.
To install and set up the emulator and its environment variables, see the documentation
at https://cloud.google.com/datastore/docs/tools/datastore-emulator.
*/
package datastore // import "cloud.google.com/go/datastore"
// resourcePrefixHeader is the name of the metadata header used to indicate
// the resource being operated on.
const resourcePrefixHeader = "google-cloud-resource-prefix"

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,32 +15,29 @@
package datastore_test
import (
"context"
"fmt"
"log"
"time"
"cloud.google.com/go/datastore"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
)
// TODO(jbd): Document other authorization methods and refer to them here.
func Example_auth() *datastore.Client {
func ExampleNewClient() {
ctx := context.Background()
// Use Google Application Default Credentials to authorize and authenticate the client.
// More information about Application Default Credentials and how to enable is at
// https://developers.google.com/identity/protocols/application-default-credentials.
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
// Use the client (see other examples).
return client
_ = client // TODO: Use client.
}
func ExampleGet() {
func ExampleClient_Get() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
type Article struct {
@@ -50,18 +47,18 @@ func ExampleGet() {
Author *datastore.Key
PublishedAt time.Time
}
key := datastore.NewKey(ctx, "Article", "articled1", 0, nil)
key := datastore.NameKey("Article", "articled1", nil)
article := &Article{}
if err := client.Get(ctx, key, article); err != nil {
log.Fatal(err)
// TODO: Handle error.
}
}
func ExamplePut() {
func ExampleClient_Put() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
type Article struct {
@@ -71,29 +68,78 @@ func ExamplePut() {
Author *datastore.Key
PublishedAt time.Time
}
newKey := datastore.NewIncompleteKey(ctx, "Article", nil)
newKey := datastore.IncompleteKey("Article", nil)
_, err = client.Put(ctx, newKey, &Article{
Title: "The title of the article",
Description: "The description of the article...",
Body: "...",
Author: datastore.NewKey(ctx, "Author", "jbd", 0, nil),
Author: datastore.NameKey("Author", "jbd", nil),
PublishedAt: time.Now(),
})
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_Put_flatten() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
type Animal struct {
Name string
Type string
Breed string
}
type Human struct {
Name string
Height int
Pet Animal `datastore:",flatten"`
}
newKey := datastore.IncompleteKey("Human", nil)
_, err = client.Put(ctx, newKey, &Human{
Name: "Susan",
Height: 67,
Pet: Animal{
Name: "Fluffy",
Type: "Cat",
Breed: "Sphynx",
},
})
if err != nil {
log.Fatal(err)
}
}
func ExampleDelete() {
func ExampleClient_Delete() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
key := datastore.NewKey(ctx, "Article", "articled1", 0, nil)
key := datastore.NameKey("Article", "articled1", nil)
if err := client.Delete(ctx, key); err != nil {
log.Fatal(err)
// TODO: Handle error.
}
}
func ExampleClient_DeleteMulti() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
var keys []*datastore.Key
for i := 1; i <= 10; i++ {
keys = append(keys, datastore.IDKey("Article", int64(i), nil))
}
if err := client.DeleteMulti(ctx, keys); err != nil {
// TODO: Handle error.
}
}
@@ -103,34 +149,57 @@ type Post struct {
Comments int
}
func ExampleGetMulti() {
func ExampleClient_GetMulti() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
datastore.NewKey(ctx, "Post", "post3", 0, nil),
datastore.NameKey("Post", "post1", nil),
datastore.NameKey("Post", "post2", nil),
datastore.NameKey("Post", "post3", nil),
}
posts := make([]Post, 3)
if err := client.GetMulti(ctx, keys, posts); err != nil {
log.Println(err)
// TODO: Handle error.
}
}
func ExamplePutMulti_slice() {
func ExampleMultiError() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
datastore.NameKey("bad-key", "bad-key", nil),
}
posts := make([]Post, 1)
if err := client.GetMulti(ctx, keys, posts); err != nil {
if merr, ok := err.(datastore.MultiError); ok {
for _, err := range merr {
// TODO: Handle error.
_ = err
}
} else {
// TODO: Handle error.
}
}
}
func ExampleClient_PutMulti_slice() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
keys := []*datastore.Key{
datastore.NameKey("Post", "post1", nil),
datastore.NameKey("Post", "post2", nil),
}
// PutMulti with a Post slice.
@@ -139,20 +208,20 @@ func ExamplePutMulti_slice() {
{Title: "Post 2", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
// TODO: Handle error.
}
}
func ExamplePutMulti_interfaceSlice() {
func ExampleClient_PutMulti_interfaceSlice() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
datastore.NameKey("Post", "post1", nil),
datastore.NameKey("Post", "post2", nil),
}
// PutMulti with an empty interface slice.
@@ -161,44 +230,57 @@ func ExamplePutMulti_interfaceSlice() {
&Post{Title: "Post 2", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
// TODO: Handle error.
}
}
func ExampleQuery() {
func ExampleNewQuery() {
// Query for Post entities.
q := datastore.NewQuery("Post")
_ = q // TODO: Use the query with Client.Run.
}
func ExampleNewQuery_options() {
// Query to order the posts by the number of comments they have received.
q := datastore.NewQuery("Post").Order("-Comments")
// Start listing from an offset and limit the results.
q = q.Offset(20).Limit(10)
_ = q // TODO: Use the query.
}
func ExampleClient_Count() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
// Count the number of the post entities.
q := datastore.NewQuery("Post")
n, err := client.Count(ctx, q)
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
log.Printf("There are %d posts.", n)
// List the posts published since yesterday.
yesterday := time.Now().Add(-24 * time.Hour)
q = datastore.NewQuery("Post").Filter("PublishedAt >", yesterday)
it := client.Run(ctx, q)
// Use the iterator.
_ = it
// Order the posts by the number of comments they have recieved.
datastore.NewQuery("Post").Order("-Comments")
// Start listing from an offset and limit the results.
datastore.NewQuery("Post").Offset(20).Limit(10)
fmt.Printf("There are %d posts.", n)
}
func ExampleTransaction() {
func ExampleClient_Run() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
// TODO: Handle error.
}
// List the posts published since yesterday.
yesterday := time.Now().Add(-24 * time.Hour)
q := datastore.NewQuery("Post").Filter("PublishedAt >", yesterday)
it := client.Run(ctx, q)
_ = it // TODO: iterate using Next.
}
func ExampleClient_NewTransaction() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
const retries = 3
@@ -209,27 +291,303 @@ func ExampleTransaction() {
Count int
}
key := datastore.NewKey(ctx, "counter", "CounterA", 0, nil)
key := datastore.NameKey("counter", "CounterA", nil)
var tx *datastore.Transaction
for i := 0; i < retries; i++ {
tx, err := client.NewTransaction(ctx)
tx, err = client.NewTransaction(ctx)
if err != nil {
break
}
var c Counter
if err := tx.Get(key, &c); err != nil && err != datastore.ErrNoSuchEntity {
if err = tx.Get(key, &c); err != nil && err != datastore.ErrNoSuchEntity {
break
}
c.Count++
if _, err := tx.Put(key, &c); err != nil {
if _, err = tx.Put(key, &c); err != nil {
break
}
// Attempt to commit the transaction. If there's a conflict, try again.
if _, err := tx.Commit(); err != datastore.ErrConcurrentTransaction {
if _, err = tx.Commit(); err != datastore.ErrConcurrentTransaction {
break
}
}
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_RunInTransaction() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
// Increment a counter.
// See https://cloud.google.com/appengine/articles/sharding_counters for
// a more scalable solution.
type Counter struct {
Count int
}
var count int
key := datastore.NameKey("Counter", "singleton", nil)
_, err = client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
var x Counter
if err := tx.Get(key, &x); err != nil && err != datastore.ErrNoSuchEntity {
return err
}
x.Count++
if _, err := tx.Put(key, &x); err != nil {
return err
}
count = x.Count
return nil
})
if err != nil {
// TODO: Handle error.
}
// The value of count is only valid once the transaction is successful
// (RunInTransaction has returned nil).
fmt.Printf("Count=%d\n", count)
}
func ExampleClient_AllocateIDs() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
var keys []*datastore.Key
for i := 0; i < 10; i++ {
keys = append(keys, datastore.IncompleteKey("Article", nil))
}
keys, err = client.AllocateIDs(ctx, keys)
if err != nil {
// TODO: Handle error.
}
_ = keys // TODO: Use keys.
}
func ExampleKey_Encode() {
key := datastore.IDKey("Article", 1, nil)
encoded := key.Encode()
fmt.Println(encoded)
// Output: EgsKB0FydGljbGUQAQ
}
func ExampleDecodeKey() {
const encoded = "EgsKB0FydGljbGUQAQ"
key, err := datastore.DecodeKey(encoded)
if err != nil {
// TODO: Handle error.
}
fmt.Println(key)
// Output: /Article,1
}
func ExampleIDKey() {
// Key with numeric ID.
k := datastore.IDKey("Article", 1, nil)
_ = k // TODO: Use key.
}
func ExampleNameKey() {
// Key with string ID.
k := datastore.NameKey("Article", "article8", nil)
_ = k // TODO: Use key.
}
func ExampleIncompleteKey() {
k := datastore.IncompleteKey("Article", nil)
_ = k // TODO: Use incomplete key.
}
func ExampleClient_GetAll() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
var posts []*Post
keys, err := client.GetAll(ctx, datastore.NewQuery("Post"), &posts)
if err != nil {
// TODO: Handle error.
}
for i, key := range keys {
fmt.Println(key)
fmt.Println(posts[i])
}
}
func ExampleClient_Mutate() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
key1 := datastore.NameKey("Post", "post1", nil)
key2 := datastore.NameKey("Post", "post2", nil)
key3 := datastore.NameKey("Post", "post3", nil)
key4 := datastore.NameKey("Post", "post4", nil)
_, err = client.Mutate(ctx,
datastore.NewInsert(key1, Post{Title: "Post 1"}),
datastore.NewUpsert(key2, Post{Title: "Post 2"}),
datastore.NewUpdate(key3, Post{Title: "Post 3"}),
datastore.NewDelete(key4))
if err != nil {
// TODO: Handle error.
}
}
func ExampleCommit_Key() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "")
if err != nil {
// TODO: Handle error.
}
var pk1, pk2 *datastore.PendingKey
// Create two posts in a single transaction.
commit, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
var err error
pk1, err = tx.Put(datastore.IncompleteKey("Post", nil), &Post{Title: "Post 1", PublishedAt: time.Now()})
if err != nil {
return err
}
pk2, err = tx.Put(datastore.IncompleteKey("Post", nil), &Post{Title: "Post 2", PublishedAt: time.Now()})
if err != nil {
return err
}
return nil
})
if err != nil {
// TODO: Handle error.
}
// Now pk1, pk2 are valid PendingKeys. Let's convert them into real keys
// using the Commit object.
k1 := commit.Key(pk1)
k2 := commit.Key(pk2)
fmt.Println(k1, k2)
}
func ExampleIterator_Next() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
it := client.Run(ctx, datastore.NewQuery("Post"))
for {
var p Post
key, err := it.Next(&p)
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(key, p)
}
}
func ExampleIterator_Cursor() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
it := client.Run(ctx, datastore.NewQuery("Post"))
for {
var p Post
_, err := it.Next(&p)
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(p)
cursor, err := it.Cursor()
if err != nil {
// TODO: Handle error.
}
// When printed, a cursor will display as a string that can be passed
// to datastore.NewCursor.
fmt.Printf("to resume with this post, use cursor %s\n", cursor)
}
}
func ExampleDecodeCursor() {
// See Query.Start for a fuller example of DecodeCursor.
// getCursor represents a function that returns a cursor from a previous
// iteration in string form.
cursorString := getCursor()
cursor, err := datastore.DecodeCursor(cursorString)
if err != nil {
// TODO: Handle error.
}
_ = cursor // TODO: Use the cursor with Query.Start or Query.End.
}
func getCursor() string { return "" }
func ExampleQuery_Start() {
// This example demonstrates how to use cursors and Query.Start
// to resume an iteration.
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
// getCursor represents a function that returns a cursor from a previous
// iteration in string form.
cursorString := getCursor()
cursor, err := datastore.DecodeCursor(cursorString)
if err != nil {
// TODO: Handle error.
}
it := client.Run(ctx, datastore.NewQuery("Post").Start(cursor))
_ = it // TODO: Use iterator.
}
func ExampleLoadStruct() {
type Player struct {
User string
Score int
}
// Normally LoadStruct would only be used inside a custom implementation of
// PropertyLoadSaver; this is for illustrative purposes only.
props := []datastore.Property{
{Name: "User", Value: "Alice"},
{Name: "Score", Value: int64(97)},
}
var p Player
if err := datastore.LoadStruct(&p, props); err != nil {
// TODO: Handle error.
}
fmt.Println(p)
// Output: {Alice 97}
}
func ExampleSaveStruct() {
type Player struct {
User string
Score int
}
p := &Player{
User: "Alice",
Score: 97,
}
props, err := datastore.SaveStruct(p)
if err != nil {
// TODO: Handle error.
}
fmt.Println(props)
// TODO(jba): make this output stable: Output: [{User Alice false} {Score 97 false}]
}

View File

@@ -1,731 +0,0 @@
// Copyright 2016 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 datastore_test
import (
"fmt"
"log"
"time"
"cloud.google.com/go/datastore"
"golang.org/x/net/context"
)
type Task struct {
Category string
Done bool
Priority int
Description string `datastore:",noindex"`
PercentComplete float64
Created time.Time
Tags []string
Collaborators []string
}
func ExampleNewIncompleteKey() {
ctx := context.Background()
// [START incomplete_key]
taskKey := datastore.NewIncompleteKey(ctx, "Task", nil)
// [END incomplete_key]
_ = taskKey // Use the task key for datastore operations.
}
func ExampleNewKey() {
ctx := context.Background()
// [START named_key]
taskKey := datastore.NewKey(ctx, "Task", "sampletask", 0, nil)
// [END named_key]
_ = taskKey // Use the task key for datastore operations.
}
func ExampleNewKey_withParent() {
ctx := context.Background()
// [START key_with_parent]
parentKey := datastore.NewKey(ctx, "TaskList", "default", 0, nil)
taskKey := datastore.NewKey(ctx, "Task", "sampleTask", 0, parentKey)
// [END key_with_parent]
_ = taskKey // Use the task key for datastore operations.
}
func ExampleNewKey_withMultipleParents() {
ctx := context.Background()
// [START key_with_multilevel_parent]
userKey := datastore.NewKey(ctx, "User", "alice", 0, nil)
parentKey := datastore.NewKey(ctx, "TaskList", "default", 0, userKey)
taskKey := datastore.NewKey(ctx, "Task", "sampleTask", 0, parentKey)
// [END key_with_multilevel_parent]
_ = taskKey // Use the task key for datastore operations.
}
func ExampleClient_Put() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START entity_with_parent]
parentKey := datastore.NewKey(ctx, "TaskList", "default", 0, nil)
key := datastore.NewIncompleteKey(ctx, "Task", parentKey)
task := Task{
Category: "Personal",
Done: false,
Priority: 4,
Description: "Learn Cloud Datastore",
}
// A complete key is assigned to the entity when it is Put.
var err error
key, err = client.Put(ctx, key, &task)
// [END entity_with_parent]
_ = err // Make sure you check err.
}
func Example_properties() {
// [START properties]
type Task struct {
Category string
Done bool
Priority int
Description string `datastore:",noindex"`
PercentComplete float64
Created time.Time
}
task := &Task{
Category: "Personal",
Done: false,
Priority: 4,
Description: "Learn Cloud Datastore",
PercentComplete: 10.0,
Created: time.Now(),
}
// [END properties]
_ = task // Use the task in a datastore Put operation.
}
func Example_sliceProperties() {
// [START array_value]
type Task struct {
Tags []string
Collaborators []string
}
task := &Task{
Tags: []string{"fun", "programming"},
Collaborators: []string{"alice", "bob"},
}
// [END array_value]
_ = task // Use the task in a datastore Put operation.
}
func Example_basicEntity() {
// [START basic_entity]
type Task struct {
Category string
Done bool
Priority float64
Description string `datastore:",noindex"`
PercentComplete float64
Created time.Time
}
task := &Task{
Category: "Personal",
Done: false,
Priority: 4,
Description: "Learn Cloud Datastore",
PercentComplete: 10.0,
Created: time.Now(),
}
// [END basic_entity]
_ = task // Use the task in a datastore Put operation.
}
func ExampleClient_Put_upsert() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
task := &Task{} // Populated with appropriate data.
key := datastore.NewIncompleteKey(ctx, "Task", nil)
// [START upsert]
key, err := client.Put(ctx, key, task)
// [END upsert]
_ = err // Make sure you check err.
_ = key // key is the complete key for the newly stored task
}
func ExampleTransaction_insert() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
task := Task{} // Populated with appropriate data.
taskKey := datastore.NewKey(ctx, "Task", "sampleTask", 0, nil)
// [START insert]
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
// We first check that there is no entity stored with the given key.
var empty Task
if err := tx.Get(taskKey, &empty); err != datastore.ErrNoSuchEntity {
return err
}
// If there was no matching entity, store it now.
_, err := tx.Put(taskKey, &task)
return err
})
// [END insert]
_ = err // Make sure you check err.
}
func ExampleClient_Get() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
taskKey := datastore.NewKey(ctx, "Task", "sampleTask", 0, nil)
// [START lookup]
var task Task
err := client.Get(ctx, taskKey, &task)
// [END lookup]
_ = err // Make sure you check err.
}
func ExampleTransaction_update() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
taskKey := datastore.NewKey(ctx, "Task", "sampleTask", 0, nil)
// [START update]
tx, err := client.NewTransaction(ctx)
if err != nil {
log.Fatalf("client.NewTransaction: %v", err)
}
var task Task
if err := tx.Get(taskKey, &task); err != nil {
log.Fatalf("tx.Get: %v", err)
}
task.Priority = 5
if _, err := tx.Put(taskKey, task); err != nil {
log.Fatalf("tx.Put: %v", err)
}
if _, err := tx.Commit(); err != nil {
log.Fatalf("tx.Commit: %v", err)
}
// [END update]
}
func ExampleClient_Delete() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
key := datastore.NewKey(ctx, "Task", "sampletask", 0, nil)
// [START delete]
err := client.Delete(ctx, key)
// [END delete]
_ = err // Make sure you check err.
}
func ExampleClient_PutMulti() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START batch_upsert]
tasks := []*Task{
{
Category: "Personal",
Done: false,
Priority: 4,
Description: "Learn Cloud Datastore",
},
{
Category: "Personal",
Done: false,
Priority: 5,
Description: "Integrate Cloud Datastore",
},
}
keys := []*datastore.Key{
datastore.NewIncompleteKey(ctx, "Task", nil),
datastore.NewIncompleteKey(ctx, "Task", nil),
}
keys, err := client.PutMulti(ctx, keys, tasks)
// [END batch_upsert]
_ = err // Make sure you check err.
_ = keys // keys now has the complete keys for the newly stored tasks.
}
func ExampleClient_GetMulti() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
var taskKeys []*datastore.Key // Populated with incomplete keys.
// [START batch_lookup]
var tasks []*Task
err := client.GetMulti(ctx, taskKeys, &tasks)
// [END batch_lookup]
_ = err // Make sure you check err.
}
func ExampleClient_DeleteMulti() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
var taskKeys []*datastore.Key // Populated with incomplete keys.
// [START batch_delete]
err := client.DeleteMulti(ctx, taskKeys)
// [END batch_delete]
_ = err // Make sure you check err.
}
func ExampleQuery_basic() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START basic_query]
query := datastore.NewQuery("Task").
Filter("Done =", false).
Filter("Priority >=", 4).
Order("-Priority")
// [END basic_query]
// [START run_query]
it := client.Run(ctx, query)
for {
var task Task
_, err := it.Next(&task)
if err == datastore.Done {
break
}
if err != nil {
log.Fatalf("Error fetching next task: %v", err)
}
fmt.Printf("Task %q, Priority %d\n", task.Description, task.Priority)
}
// [END run_query]
}
func ExampleQuery_propertyFilter() {
// [START property_filter]
query := datastore.NewQuery("Task").Filter("Done =", false)
// [END property_filter]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_compositeFilter() {
// [START composite_filter]
query := datastore.NewQuery("Task").Filter("Done =", false).Filter("Priority =", 4)
// [END composite_filter]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_keyFilter() {
ctx := context.Background()
// [START key_filter]
key := datastore.NewKey(ctx, "Task", "someTask", 0, nil)
query := datastore.NewQuery("Task").Filter("__key__ >", key)
// [END key_filter]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_sortAscending() {
// [START ascending_sort]
query := datastore.NewQuery("Task").Order("created")
// [END ascending_sort]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_sortDescending() {
// [START descending_sort]
query := datastore.NewQuery("Task").Order("-created")
// [END descending_sort]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_sortMulti() {
// [START multi_sort]
query := datastore.NewQuery("Task").Order("-priority").Order("created")
// [END multi_sort]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_kindless() {
var lastSeenKey *datastore.Key
// [START kindless_query]
query := datastore.NewQuery("").Filter("__key__ >", lastSeenKey)
// [END kindless_query]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_Ancestor() {
ctx := context.Background()
// [START ancestor_query]
ancestor := datastore.NewKey(ctx, "TaskList", "default", 0, nil)
query := datastore.NewQuery("Task").Ancestor(ancestor)
// [END ancestor_query]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_Project() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START projection_query]
query := datastore.NewQuery("Task").Project("Priority", "PercentComplete")
// [END projection_query]
// [START run_query_projection]
var priorities []int
var percents []float64
it := client.Run(ctx, query)
for {
var task Task
if _, err := it.Next(&task); err == datastore.Done {
break
} else if err != nil {
log.Fatal(err)
}
priorities = append(priorities, task.Priority)
percents = append(percents, task.PercentComplete)
}
// [END run_query_projection]
}
func ExampleQuery_KeysOnly() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START keys_only_query]
query := datastore.NewQuery("Task").KeysOnly()
// [END keys_only_query]
// [START run_keys_only_query]
keys, err := client.GetAll(ctx, query, nil)
// [END run_keys_only_query]
_ = err // Make sure you check err.
_ = keys // Keys contains keys for all stored tasks.
}
func ExampleQuery_Distinct() {
// [START distinct_query]
query := datastore.NewQuery("Task").
Project("Priority", "PercentComplete").
Distinct().
Order("Category").Order("Priority")
// [END distinct_query]
_ = query // Use client.Run or client.GetAll to execute the query.
// [START distinct_on_query]
// DISTINCT ON not supported in Go API
// [END distinct_on_query]
}
func ExampleQuery_Filter_arrayInequality() {
// [START array_value_inequality_range]
query := datastore.NewQuery("Task").
Filter("Tag >", "learn").
Filter("Tag <", "math")
// [END array_value_inequality_range]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_Filter_arrayEquality() {
// [START array_value_equality]
query := datastore.NewQuery("Task").
Filter("Tag =", "fun").
Filter("Tag =", "programming")
// [END array_value_equality]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_Filter_inequality() {
// [START inequality_range]
query := datastore.NewQuery("Task").
Filter("Created >", time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)).
Filter("Created <", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
// [END inequality_range]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_Filter_invalidInequality() {
// [START inequality_invalid]
query := datastore.NewQuery("Task").
Filter("Created >", time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)).
Filter("Priority >", 3)
// [END inequality_invalid]
_ = query // The query is invalid.
}
func ExampleQuery_Filter_mixed() {
// [START equal_and_inequality_range]
query := datastore.NewQuery("Task").
Filter("Priority =", 4).
Filter("Done =", false).
Filter("Created >", time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)).
Filter("Created <", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
// [END equal_and_inequality_range]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_inequalitySort() {
// [START inequality_sort]
query := datastore.NewQuery("Task").
Filter("Priority >", 3).
Order("Priority").
Order("Created")
// [END inequality_sort]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_invalidInequalitySortA() {
// [START inequality_sort_invalid_not_same]
query := datastore.NewQuery("Task").
Filter("Priority >", 3).
Order("Created")
// [END inequality_sort_invalid_not_same]
_ = query // The query is invalid.
}
func ExampleQuery_invalidInequalitySortB() {
// [START inequality_sort_invalid_not_first]
query := datastore.NewQuery("Task").
Filter("Priority >", 3).
Order("Created").
Order("Priority")
// [END inequality_sort_invalid_not_first]
_ = query // The query is invalid.
}
func ExampleQuery_Limit() {
// [START limit]
query := datastore.NewQuery("Task").Limit(5)
// [END limit]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleIterator_Cursor() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
cursorStr := ""
// [START cursor_paging]
const pageSize = 5
query := datastore.NewQuery("Tasks").Limit(pageSize)
if cursorStr != "" {
cursor, err := datastore.DecodeCursor(cursorStr)
if err != nil {
log.Fatalf("Bad cursor %q: %v", cursorStr, err)
}
query = query.Start(cursor)
}
// Read the tasks.
var tasks []Task
var task Task
it := client.Run(ctx, query)
_, err := it.Next(&task)
for err == nil {
tasks = append(tasks, task)
_, err = it.Next(&task)
}
if err != datastore.Done {
log.Fatalf("Failed fetching results: %v", err)
}
// Get the cursor for the next page of results.
nextCursor, err := it.Cursor()
// [END cursor_paging]
_ = err // Check the error.
_ = nextCursor // Use nextCursor.String as the next page's token.
}
func ExampleQuery_EventualConsistency() {
ctx := context.Background()
// [START eventual_consistent_query]
ancestor := datastore.NewKey(ctx, "TaskList", "default", 0, nil)
query := datastore.NewQuery("Task").Ancestor(ancestor).EventualConsistency()
// [END eventual_consistent_query]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func ExampleQuery_unindexed() {
// [START unindexed_property_query]
query := datastore.NewQuery("Tasks").Filter("Description =", "A task description")
// [END unindexed_property_query]
_ = query // Use client.Run or client.GetAll to execute the query.
}
func Example_explodingProperties() {
// [START exploding_properties]
task := &Task{
Tags: []string{"fun", "programming", "learn"},
Collaborators: []string{"alice", "bob", "charlie"},
Created: time.Now(),
}
// [END exploding_properties]
_ = task // Use the task in a datastore Put operation.
}
func Example_Transaction() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
var to, from *datastore.Key
// [START transactional_update]
type BankAccount struct {
Balance int
}
const amount = 50
keys := []*datastore.Key{to, from}
tx, err := client.NewTransaction(ctx)
if err != nil {
log.Fatalf("client.NewTransaction: %v", err)
}
accs := make([]BankAccount, 2)
if err := tx.GetMulti(keys, accs); err != nil {
tx.Rollback()
log.Fatalf("tx.GetMulti: %v", err)
}
accs[0].Balance += amount
accs[1].Balance -= amount
if _, err := tx.PutMulti(keys, accs); err != nil {
tx.Rollback()
log.Fatalf("tx.PutMulti: %v", err)
}
if _, err = tx.Commit(); err != nil {
log.Fatalf("tx.Commit: %v", err)
}
// [END transactional_update]
}
func Example_Client_RunInTransaction() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
var to, from *datastore.Key
// [START transactional_retry]
type BankAccount struct {
Balance int
}
const amount = 50
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
keys := []*datastore.Key{to, from}
accs := make([]BankAccount, 2)
if err := tx.GetMulti(keys, accs); err != nil {
return err
}
accs[0].Balance += amount
accs[1].Balance -= amount
_, err := tx.PutMulti(keys, accs)
return err
})
// [END transactional_retry]
_ = err // Check error.
}
func ExampleTransaction_getOrCreate() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
key := datastore.NewKey(ctx, "Task", "sampletask", 0, nil)
// [START transactional_get_or_create]
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
var task Task
if err := tx.Get(key, &task); err != datastore.ErrNoSuchEntity {
return err
}
_, err := tx.Put(key, &Task{
Category: "Personal",
Done: false,
Priority: 4,
Description: "Learn Cloud Datastore",
})
return err
})
// [END transactional_get_or_create]
_ = err // Check error.
}
func ExampleTransaction_runQuery() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START transactional_single_entity_group_read_only]
tx, err := client.NewTransaction(ctx)
if err != nil {
log.Fatalf("client.NewTransaction: %v", err)
}
defer tx.Rollback() // Transaction only used for read.
ancestor := datastore.NewKey(ctx, "TaskList", "default", 0, nil)
query := datastore.NewQuery("Task").Ancestor(ancestor).Transaction(tx)
var tasks []Task
_, err = client.GetAll(ctx, query, &tasks)
// [END transactional_single_entity_group_read_only]
_ = err // Check error.
}
func Example_metadataNamespaces() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START namespace_run_query]
const (
startNamespace = "g"
endNamespace = "h"
)
query := datastore.NewQuery("__namespace__").
Filter("__key__ >=", startNamespace).
Filter("__key__ <", endNamespace).
KeysOnly()
keys, err := client.GetAll(ctx, query, nil)
if err != nil {
log.Fatalf("client.GetAll: %v", err)
}
namespaces := make([]string, 0, len(keys))
for _, k := range keys {
namespaces = append(namespaces, k.Name())
}
// [END namespace_run_query]
}
func Example_metadataKinds() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START kind_run_query]
query := datastore.NewQuery("__kind__").KeysOnly()
keys, err := client.GetAll(ctx, query, nil)
if err != nil {
log.Fatalf("client.GetAll: %v", err)
}
kinds := make([]string, 0, len(keys))
for _, k := range keys {
kinds = append(kinds, k.Name())
}
// [END kind_run_query]
}
func Example_metadataProperties() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START property_run_query]
query := datastore.NewQuery("__property__").KeysOnly()
keys, err := client.GetAll(ctx, query, nil)
if err != nil {
log.Fatalf("client.GetAll: %v", err)
}
props := make(map[string][]string) // Map from kind to slice of properties.
for _, k := range keys {
prop := k.Name()
kind := k.Parent().Name()
props[kind] = append(props[kind], prop)
}
// [END property_run_query]
}
func Example_metadataPropertiesForKind() {
ctx := context.Background()
client, _ := datastore.NewClient(ctx, "my-proj")
// [START property_by_kind_run_query]
kindKey := datastore.NewKey(ctx, "__kind__", "Task", 0, nil)
query := datastore.NewQuery("__property__").Ancestor(kindKey)
type Prop struct {
Repr []string `datastore:"property_representation"`
}
var props []Prop
keys, err := client.GetAll(ctx, query, &props)
// [END property_by_kind_run_query]
_ = err // Check error.
_ = keys // Use keys to find property names, and props for their representations.
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,8 +15,13 @@
package datastore
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
"reflect"
"sort"
"strings"
@@ -25,24 +30,121 @@ import (
"time"
"cloud.google.com/go/internal/testutil"
"golang.org/x/net/context"
"cloud.google.com/go/rpcreplay"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// TODO(djd): Make test entity clean up more robust: some test entities may
// be left behind if tests are aborted, the transport fails, etc.
var timeNow = time.Now()
// suffix is a timestamp-based suffix which is appended to key names,
// particularly for the root keys of entity groups. This reduces flakiness
// when the tests are run in parallel.
var suffix = fmt.Sprintf("-t%d", time.Now().UnixNano())
var suffix string
func newClient(ctx context.Context, t *testing.T) *Client {
const replayFilename = "datastore.replay"
type replayInfo struct {
ProjectID string
Time time.Time
}
var (
record = flag.Bool("record", false, "record RPCs")
newTestClient = func(ctx context.Context, t *testing.T) *Client {
return newClient(ctx, t, nil)
}
)
func TestMain(m *testing.M) {
os.Exit(testMain(m))
}
func testMain(m *testing.M) int {
flag.Parse()
if testing.Short() {
if *record {
log.Fatal("cannot combine -short and -record")
}
if testutil.CanReplay(replayFilename) {
initReplay()
}
} else if *record {
if testutil.ProjID() == "" {
log.Fatal("must record with a project ID")
}
b, err := json.Marshal(replayInfo{
ProjectID: testutil.ProjID(),
Time: timeNow,
})
if err != nil {
log.Fatal(err)
}
rec, err := rpcreplay.NewRecorder(replayFilename, b)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := rec.Close(); err != nil {
log.Fatalf("closing recorder: %v", err)
}
}()
newTestClient = func(ctx context.Context, t *testing.T) *Client {
return newClient(ctx, t, rec.DialOptions())
}
log.Printf("recording to %s", replayFilename)
}
suffix = fmt.Sprintf("-t%d", timeNow.UnixNano())
return m.Run()
}
func initReplay() {
rep, err := rpcreplay.NewReplayer(replayFilename)
if err != nil {
log.Fatal(err)
}
defer rep.Close()
var ri replayInfo
if err := json.Unmarshal(rep.Initial(), &ri); err != nil {
log.Fatalf("unmarshaling initial replay info: %v", err)
}
timeNow = ri.Time.In(time.Local)
conn, err := rep.Connection()
if err != nil {
log.Fatal(err)
}
newTestClient = func(ctx context.Context, t *testing.T) *Client {
client, err := NewClient(ctx, ri.ProjectID, option.WithGRPCConn(conn))
if err != nil {
t.Fatalf("NewClient: %v", err)
}
return client
}
log.Printf("replaying from %s", replayFilename)
}
func newClient(ctx context.Context, t *testing.T, dialOpts []grpc.DialOption) *Client {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ts := testutil.TokenSource(ctx, ScopeDatastore)
if ts == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}
client, err := NewClient(ctx, testutil.ProjID(), option.WithTokenSource(ts))
opts := []option.ClientOption{option.WithTokenSource(ts)}
for _, opt := range dialOpts {
opts = append(opts, option.WithGRPCDialOption(opt))
}
client, err := NewClient(ctx, testutil.ProjID(), opts...)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
@@ -50,11 +152,8 @@ func newClient(ctx context.Context, t *testing.T) *Client {
}
func TestBasics(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx, _ := context.WithTimeout(context.Background(), time.Second*20)
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type X struct {
@@ -63,8 +162,8 @@ func TestBasics(t *testing.T) {
T time.Time
}
x0 := X{66, "99", time.Now().Truncate(time.Millisecond)}
k, err := client.Put(ctx, NewIncompleteKey(ctx, "BasicsX", nil), &x0)
x0 := X{66, "99", timeNow.Truncate(time.Millisecond)}
k, err := client.Put(ctx, IncompleteKey("BasicsX", nil), &x0)
if err != nil {
t.Fatalf("client.Put: %v", err)
}
@@ -77,23 +176,56 @@ func TestBasics(t *testing.T) {
if err != nil {
t.Errorf("client.Delete: %v", err)
}
if !reflect.DeepEqual(x0, x1) {
if !testutil.Equal(x0, x1) {
t.Errorf("compare: x0=%v, x1=%v", x0, x1)
}
}
func TestListValues(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
func TestTopLevelKeyLoaded(t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), time.Second*20)
client := newTestClient(ctx, t)
defer client.Close()
completeKey := NameKey("EntityWithKey", "myent", nil)
type EntityWithKey struct {
I int
S string
K *Key `datastore:"__key__"`
}
in := &EntityWithKey{
I: 12,
S: "abcd",
}
k, err := client.Put(ctx, completeKey, in)
if err != nil {
t.Fatalf("client.Put: %v", err)
}
var e EntityWithKey
err = client.Get(ctx, k, &e)
if err != nil {
t.Fatalf("client.Get: %v", err)
}
// The two keys should be absolutely identical.
if !testutil.Equal(e.K, k) {
t.Fatalf("e.K not equal to k; got %#v, want %#v", e.K, k)
}
}
func TestListValues(t *testing.T) {
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
p0 := PropertyList{
{Name: "L", Value: []interface{}{int64(12), "string", true}},
}
k, err := client.Put(ctx, NewIncompleteKey(ctx, "ListValue", nil), &p0)
k, err := client.Put(ctx, IncompleteKey("ListValue", nil), &p0)
if err != nil {
t.Fatalf("client.Put: %v", err)
}
@@ -101,7 +233,7 @@ func TestListValues(t *testing.T) {
if err := client.Get(ctx, k, &p1); err != nil {
t.Errorf("client.Get: %v", err)
}
if !reflect.DeepEqual(p0, p1) {
if !testutil.Equal(p0, p1) {
t.Errorf("compare:\np0=%v\np1=%#v", p0, p1)
}
if err = client.Delete(ctx, k); err != nil {
@@ -110,26 +242,24 @@ func TestListValues(t *testing.T) {
}
func TestGetMulti(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type X struct {
I int
}
p := NewKey(ctx, "X", "x"+suffix, 0, nil)
p := NameKey("X", "x"+suffix, nil)
cases := []struct {
key *Key
put bool
}{
{key: NewKey(ctx, "X", "item1", 0, p), put: true},
{key: NewKey(ctx, "X", "item2", 0, p), put: false},
{key: NewKey(ctx, "X", "item3", 0, p), put: false},
{key: NewKey(ctx, "X", "item4", 0, p), put: true},
{key: NameKey("X", "item1", p), put: true},
{key: NameKey("X", "item2", p), put: false},
{key: NameKey("X", "item3", p), put: false},
{key: NameKey("X", "item3", p), put: false},
{key: NameKey("X", "item4", p), put: true},
}
var src, dst []*X
@@ -183,11 +313,8 @@ func (z Z) String() string {
}
func TestUnindexableValues(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
x1500 := strings.Repeat("x", 1500)
@@ -206,7 +333,7 @@ func TestUnindexableValues(t *testing.T) {
{in: Z{K: []byte(x1501)}, wantErr: false},
}
for _, tt := range testCases {
_, err := client.Put(ctx, NewIncompleteKey(ctx, "BasicsZ", nil), &tt.in)
_, err := client.Put(ctx, IncompleteKey("BasicsZ", nil), &tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("client.Put %s got err %v, want err %t", tt.in, err, tt.wantErr)
}
@@ -214,11 +341,8 @@ func TestUnindexableValues(t *testing.T) {
}
func TestNilKey(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
testCases := []struct {
@@ -229,7 +353,7 @@ func TestNilKey(t *testing.T) {
{in: K0{}, wantErr: false},
}
for _, tt := range testCases {
_, err := client.Put(ctx, NewIncompleteKey(ctx, "NilKey", nil), &tt.in)
_, err := client.Put(ctx, IncompleteKey("NilKey", nil), &tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("client.Put %s got err %v, want err %t", tt.in, err, tt.wantErr)
}
@@ -248,11 +372,11 @@ type SQTestCase struct {
wantSum int
}
func testSmallQueries(t *testing.T, ctx context.Context, client *Client, parent *Key, children []*SQChild,
func testSmallQueries(ctx context.Context, t *testing.T, client *Client, parent *Key, children []*SQChild,
testCases []SQTestCase, extraTests ...func()) {
keys := make([]*Key, len(children))
for i := range keys {
keys[i] = NewIncompleteKey(ctx, "SQChild", parent)
keys[i] = IncompleteKey("SQChild", parent)
}
keys, err := client.PutMulti(ctx, keys, children)
if err != nil {
@@ -299,15 +423,12 @@ func testSmallQueries(t *testing.T, ctx context.Context, client *Client, parent
}
func TestFilters(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
parent := NewKey(ctx, "SQParent", "TestFilters"+suffix, 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
parent := NameKey("SQParent", "TestFilters"+suffix, nil)
now := timeNow.Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 0, T: now, U: now},
{I: 1, T: now, U: now},
@@ -319,7 +440,7 @@ func TestFilters(t *testing.T) {
{I: 7, T: now, U: now},
}
baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now)
testSmallQueries(t, ctx, client, parent, children, []SQTestCase{
testSmallQueries(ctx, t, client, parent, children, []SQTestCase{
{
"I>1",
baseQuery.Filter("I>", 1),
@@ -360,7 +481,7 @@ func TestFilters(t *testing.T) {
if err != nil {
t.Errorf("client.GetAll: %v", err)
}
if !reflect.DeepEqual(got, want) {
if !testutil.Equal(got, want) {
t.Errorf("compare: got=%v, want=%v", got, want)
}
}, func() {
@@ -379,22 +500,21 @@ func TestFilters(t *testing.T) {
if err != nil {
t.Errorf("client.GetAll: %v", err)
}
if !reflect.DeepEqual(got, want) {
if !testutil.Equal(got, want) {
t.Errorf("compare: got=%v, want=%v", got, want)
}
})
}
type ckey struct{}
func TestLargeQuery(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
parent := NewKey(ctx, "LQParent", "TestFilters"+suffix, 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
parent := NameKey("LQParent", "TestFilters"+suffix, nil)
now := timeNow.Truncate(time.Millisecond).Unix()
// Make a large number of children entities.
const n = 800
@@ -402,7 +522,7 @@ func TestLargeQuery(t *testing.T) {
keys := make([]*Key, 0, n)
for i := 0; i < n; i++ {
children = append(children, &SQChild{I: i, T: now, U: now})
keys = append(keys, NewIncompleteKey(ctx, "SQChild", parent))
keys = append(keys, IncompleteKey("SQChild", parent))
}
// Store using PutMulti in batches.
@@ -510,11 +630,12 @@ func TestLargeQuery(t *testing.T) {
go func(count, limit, offset, want int) {
defer wg.Done()
ctx := context.WithValue(ctx, ckey{}, fmt.Sprintf("c=%d,l=%d,o=%d", count, limit, offset))
// Run iterator through count calls to Next.
it := client.Run(ctx, q.Limit(limit).Offset(offset).KeysOnly())
for i := 0; i < count; i++ {
_, err := it.Next(nil)
if err == Done {
if err == iterator.Done {
break
}
if err != nil {
@@ -536,7 +657,7 @@ func TestLargeQuery(t *testing.T) {
_, err = it.Next(&entity)
switch {
case want == -1:
if err != Done {
if err != iterator.Done {
t.Errorf("count=%d, limit=%d, offset=%d: it.Next from cursor %v, want Done", count, limit, offset, err)
}
case err != nil:
@@ -546,7 +667,6 @@ func TestLargeQuery(t *testing.T) {
}
}(tt.count, tt.limit, tt.offset, tt.want)
}
wg.Wait()
}
@@ -554,22 +674,19 @@ func TestEventualConsistency(t *testing.T) {
// TODO(jba): either make this actually test eventual consistency, or
// delete it. Currently it behaves the same with or without the
// EventualConsistency call.
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
parent := NewKey(ctx, "SQParent", "TestEventualConsistency"+suffix, 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
parent := NameKey("SQParent", "TestEventualConsistency"+suffix, nil)
now := timeNow.Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 0, T: now, U: now},
{I: 1, T: now, U: now},
{I: 2, T: now, U: now},
}
query := NewQuery("SQChild").Ancestor(parent).Filter("T =", now).EventualConsistency()
testSmallQueries(t, ctx, client, parent, children, nil, func() {
testSmallQueries(ctx, t, client, parent, children, nil, func() {
got, err := client.Count(ctx, query)
if err != nil {
t.Fatalf("Count: %v", err)
@@ -581,15 +698,12 @@ func TestEventualConsistency(t *testing.T) {
}
func TestProjection(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
parent := NewKey(ctx, "SQParent", "TestProjection"+suffix, 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
parent := NameKey("SQParent", "TestProjection"+suffix, nil)
now := timeNow.Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 1 << 0, J: 100, T: now, U: now},
{I: 1 << 1, J: 100, T: now, U: now},
@@ -598,7 +712,7 @@ func TestProjection(t *testing.T) {
{I: 1 << 4, J: 300, T: now, U: now},
}
baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now).Filter("J>", 150)
testSmallQueries(t, ctx, client, parent, children, []SQTestCase{
testSmallQueries(ctx, t, client, parent, children, []SQTestCase{
{
"project",
baseQuery.Project("J"),
@@ -611,6 +725,12 @@ func TestProjection(t *testing.T) {
2,
200 + 300,
},
{
"distinct on",
baseQuery.Project("J").DistinctOn("J"),
2,
200 + 300,
},
{
"project on meaningful (GD_WHEN) field",
baseQuery.Project("U"),
@@ -621,16 +741,13 @@ func TestProjection(t *testing.T) {
}
func TestAllocateIDs(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
keys := make([]*Key, 5)
for i := range keys {
keys[i] = NewIncompleteKey(ctx, "AllocID", nil)
keys[i] = IncompleteKey("AllocID", nil)
}
keys, err := client.AllocateIDs(ctx, keys)
if err != nil {
@@ -647,11 +764,8 @@ func TestAllocateIDs(t *testing.T) {
}
func TestGetAllWithFieldMismatch(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type Fat struct {
@@ -665,10 +779,10 @@ func TestGetAllWithFieldMismatch(t *testing.T) {
// by default, which prevents a test from being flaky.
// See https://cloud.google.com/appengine/docs/go/datastore/queries#Go_Data_consistency
// for more information.
parent := NewKey(ctx, "SQParent", "TestGetAllWithFieldMismatch"+suffix, 0, nil)
parent := NameKey("SQParent", "TestGetAllWithFieldMismatch"+suffix, nil)
putKeys := make([]*Key, 3)
for i := range putKeys {
putKeys[i] = NewKey(ctx, "GetAllThing", "", int64(10+i), parent)
putKeys[i] = IDKey("GetAllThing", int64(10+i), parent)
_, err := client.Put(ctx, putKeys[i], &Fat{X: 20 + i, Y: 30 + i})
if err != nil {
t.Fatalf("client.Put: %v", err)
@@ -682,10 +796,10 @@ func TestGetAllWithFieldMismatch(t *testing.T) {
{X: 22},
}
getKeys, err := client.GetAll(ctx, NewQuery("GetAllThing").Ancestor(parent), &got)
if len(getKeys) != 3 && !reflect.DeepEqual(getKeys, putKeys) {
if len(getKeys) != 3 && !testutil.Equal(getKeys, putKeys) {
t.Errorf("client.GetAll: keys differ\ngetKeys=%v\nputKeys=%v", getKeys, putKeys)
}
if !reflect.DeepEqual(got, want) {
if !testutil.Equal(got, want) {
t.Errorf("client.GetAll: entities differ\ngot =%v\nwant=%v", got, want)
}
if _, ok := err.(*ErrFieldMismatch); !ok {
@@ -694,11 +808,8 @@ func TestGetAllWithFieldMismatch(t *testing.T) {
}
func TestKindlessQueries(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type Dee struct {
@@ -710,13 +821,13 @@ func TestKindlessQueries(t *testing.T) {
Pling string
}
parent := NewKey(ctx, "Tweedle", "tweedle"+suffix, 0, nil)
parent := NameKey("Tweedle", "tweedle"+suffix, nil)
keys := []*Key{
NewKey(ctx, "Dee", "dee0", 0, parent),
NewKey(ctx, "Dum", "dum1", 0, parent),
NewKey(ctx, "Dum", "dum2", 0, parent),
NewKey(ctx, "Dum", "dum3", 0, parent),
NameKey("Dee", "dee0", parent),
NameKey("Dum", "dum1", parent),
NameKey("Dum", "dum2", parent),
NameKey("Dum", "dum3", parent),
}
src := []interface{}{
&Dee{1, "binary0001"},
@@ -800,7 +911,7 @@ loop:
Why, Pling string
}
_, err := iter.Next(&dst)
if err == Done {
if err == iterator.Done {
break
}
if err != nil {
@@ -810,7 +921,7 @@ loop:
got = append(got, dst.I)
}
sort.Ints(got)
if !reflect.DeepEqual(got, tc.want) {
if !testutil.Equal(got, tc.want) {
t.Errorf("elems %q: got %+v want %+v", tc.desc, got, tc.want)
continue
}
@@ -818,11 +929,8 @@ loop:
}
func TestTransaction(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type Counter struct {
@@ -866,8 +974,8 @@ func TestTransaction(t *testing.T) {
for i, tt := range tests {
// Put a new counter.
c := &Counter{N: 10, T: time.Now()}
key, err := client.Put(ctx, NewIncompleteKey(ctx, "TransCounter", nil), c)
c := &Counter{N: 10, T: timeNow}
key, err := client.Put(ctx, IncompleteKey("TransCounter", nil), c)
if err != nil {
t.Errorf("%s: client.Put: %v", tt.desc, err)
continue
@@ -894,7 +1002,7 @@ func TestTransaction(t *testing.T) {
}
if tt.causeConflict[attempts-1] {
c.N += 1
c.N++
if _, err := client.Put(ctx, key, &c); err != nil {
return err
}
@@ -923,12 +1031,54 @@ func TestTransaction(t *testing.T) {
}
}
func TestNilPointers(t *testing.T) {
func TestReadOnlyTransaction(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newClient(ctx, t, nil)
defer client.Close()
type value struct{ N int }
// Put a value.
const n = 5
v := &value{N: n}
key, err := client.Put(ctx, IncompleteKey("roTxn", nil), v)
if err != nil {
t.Fatal(err)
}
defer client.Delete(ctx, key)
// Read it from a read-only transaction.
_, err = client.RunInTransaction(ctx, func(tx *Transaction) error {
if err := tx.Get(key, v); err != nil {
return err
}
return nil
}, ReadOnly)
if err != nil {
t.Fatal(err)
}
if v.N != n {
t.Fatalf("got %d, want %d", v.N, n)
}
// Attempting to write from a read-only transaction is an error.
_, err = client.RunInTransaction(ctx, func(tx *Transaction) error {
if _, err := tx.Put(key, v); err != nil {
return err
}
return nil
}, ReadOnly)
if err == nil {
t.Fatal("got nil, want error")
}
}
func TestNilPointers(t *testing.T) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
type X struct {
@@ -936,7 +1086,7 @@ func TestNilPointers(t *testing.T) {
}
src := []*X{{"zero"}, {"one"}}
keys := []*Key{NewIncompleteKey(ctx, "NilX", nil), NewIncompleteKey(ctx, "NilX", nil)}
keys := []*Key{IncompleteKey("NilX", nil), IncompleteKey("NilX", nil)}
keys, err := client.PutMulti(ctx, keys, src)
if err != nil {
t.Fatalf("PutMulti: %v", err)
@@ -946,7 +1096,7 @@ func TestNilPointers(t *testing.T) {
xs := make([]*X, 2)
if err := client.GetMulti(ctx, keys, xs); err != nil {
t.Errorf("GetMulti: %v", err)
} else if !reflect.DeepEqual(xs, src) {
} else if !testutil.Equal(xs, src) {
t.Errorf("GetMulti fetched %v, want %v", xs, src)
}
@@ -956,17 +1106,16 @@ func TestNilPointers(t *testing.T) {
t.Errorf("Get: err %v; want %v", err, want)
}
// Test that deleting with duplicate keys work.
keys = append(keys, keys...)
if err := client.DeleteMulti(ctx, keys); err != nil {
t.Errorf("Delete: %v", err)
}
}
func TestNestedRepeatedElementNoIndex(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t)
client := newTestClient(ctx, t)
defer client.Close()
type Inner struct {
@@ -983,7 +1132,7 @@ func TestNestedRepeatedElementNoIndex(t *testing.T) {
},
}
key := NewKey(ctx, "Nested", "Nested"+suffix, 0, nil)
key := NameKey("Nested", "Nested"+suffix, nil)
if _, err := client.Put(ctx, key, m); err != nil {
t.Fatalf("client.Put: %v", err)
}
@@ -991,3 +1140,143 @@ func TestNestedRepeatedElementNoIndex(t *testing.T) {
t.Fatalf("client.Delete: %v", err)
}
}
func TestPointerFields(t *testing.T) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
want := populatedPointers()
key, err := client.Put(ctx, IncompleteKey("pointers", nil), want)
if err != nil {
t.Fatal(err)
}
var got Pointers
if err := client.Get(ctx, key, &got); err != nil {
t.Fatal(err)
}
if got.Pi == nil || *got.Pi != *want.Pi {
t.Errorf("Pi: got %v, want %v", got.Pi, *want.Pi)
}
if got.Ps == nil || *got.Ps != *want.Ps {
t.Errorf("Ps: got %v, want %v", got.Ps, *want.Ps)
}
if got.Pb == nil || *got.Pb != *want.Pb {
t.Errorf("Pb: got %v, want %v", got.Pb, *want.Pb)
}
if got.Pf == nil || *got.Pf != *want.Pf {
t.Errorf("Pf: got %v, want %v", got.Pf, *want.Pf)
}
if got.Pg == nil || *got.Pg != *want.Pg {
t.Errorf("Pg: got %v, want %v", got.Pg, *want.Pg)
}
if got.Pt == nil || !got.Pt.Equal(*want.Pt) {
t.Errorf("Pt: got %v, want %v", got.Pt, *want.Pt)
}
}
func TestMutate(t *testing.T) {
// test Client.Mutate
testMutate(t, func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error) {
return client.Mutate(ctx, muts...)
})
// test Transaction.Mutate
testMutate(t, func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error) {
var pkeys []*PendingKey
commit, err := client.RunInTransaction(ctx, func(tx *Transaction) error {
var err error
pkeys, err = tx.Mutate(muts...)
return err
})
if err != nil {
return nil, err
}
var keys []*Key
for _, pk := range pkeys {
keys = append(keys, commit.Key(pk))
}
return keys, nil
})
}
func testMutate(t *testing.T, mutate func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error)) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
type T struct{ I int }
check := func(k *Key, want interface{}) {
var x T
err := client.Get(ctx, k, &x)
switch want := want.(type) {
case error:
if err != want {
t.Errorf("key %s: got error %v, want %v", k, err, want)
}
case int:
if err != nil {
t.Fatalf("key %s: %v", k, err)
}
if x.I != want {
t.Errorf("key %s: got %d, want %d", k, x.I, want)
}
default:
panic("check: bad arg")
}
}
keys, err := mutate(ctx, client,
NewInsert(IncompleteKey("t", nil), &T{1}),
NewUpsert(IncompleteKey("t", nil), &T{2}),
)
if err != nil {
t.Fatal(err)
}
check(keys[0], 1)
check(keys[1], 2)
_, err = mutate(ctx, client,
NewUpdate(keys[0], &T{3}),
NewDelete(keys[1]),
)
if err != nil {
t.Fatal(err)
}
check(keys[0], 3)
check(keys[1], ErrNoSuchEntity)
_, err = mutate(ctx, client, NewInsert(keys[0], &T{4}))
if got, want := status.Code(err), codes.AlreadyExists; got != want {
t.Errorf("Insert existing key: got %s, want %s", got, want)
}
_, err = mutate(ctx, client, NewUpdate(keys[1], &T{4}))
if got, want := status.Code(err), codes.NotFound; got != want {
t.Errorf("Update non-existing key: got %s, want %s", got, want)
}
}
func TestDetectProjectID(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
creds := testutil.Credentials(ctx, ScopeDatastore)
if creds == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}
// Use creds with project ID.
if _, err := NewClient(ctx, DetectProjectID, option.WithCredentials(creds)); err != nil {
t.Errorf("NewClient: %v", err)
}
ts := testutil.ErroringTokenSource{}
// Try to use creds without project ID.
_, err := NewClient(ctx, DetectProjectID, option.WithTokenSource(ts))
if err == nil || err.Error() != "datastore: see the docs on DetectProjectID" {
t.Errorf("expected an error while using TokenSource that does not have a project ID")
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@ package datastore
import (
"bytes"
"context"
"encoding/base64"
"encoding/gob"
"errors"
@@ -23,50 +24,30 @@ import (
"strings"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
// Key represents the datastore key for a stored entity, and is immutable.
// Key represents the datastore key for a stored entity.
type Key struct {
kind string
id int64
name string
parent *Key
// Kind cannot be empty.
Kind string
// Either ID or Name must be zero for the Key to be valid.
// If both are zero, the Key is incomplete.
ID int64
Name string
// Parent must either be a complete Key or nil.
Parent *Key
namespace string
// Namespace provides the ability to partition your data for multiple
// tenants. In most cases, it is not necessary to specify a namespace.
// See docs on datastore multitenancy for details:
// https://cloud.google.com/datastore/docs/concepts/multitenancy
Namespace string
}
func (k *Key) Kind() string {
return k.kind
}
func (k *Key) ID() int64 {
return k.id
}
func (k *Key) Name() string {
return k.name
}
func (k *Key) Parent() *Key {
return k.parent
}
func (k *Key) SetParent(v *Key) {
if v.Incomplete() {
panic("can't set an incomplete key as parent")
}
k.parent = v
}
func (k *Key) Namespace() string {
return k.namespace
}
// Complete returns whether the key does not refer to a stored entity.
// Incomplete reports whether the key does not refer to a stored entity.
func (k *Key) Incomplete() bool {
return k.name == "" && k.id == 0
return k.Name == "" && k.ID == 0
}
// valid returns whether the key is valid.
@@ -74,18 +55,18 @@ func (k *Key) valid() bool {
if k == nil {
return false
}
for ; k != nil; k = k.parent {
if k.kind == "" {
for ; k != nil; k = k.Parent {
if k.Kind == "" {
return false
}
if k.name != "" && k.id != 0 {
if k.Name != "" && k.ID != 0 {
return false
}
if k.parent != nil {
if k.parent.Incomplete() {
if k.Parent != nil {
if k.Parent.Incomplete() {
return false
}
if k.parent.namespace != k.namespace {
if k.Parent.Namespace != k.Namespace {
return false
}
}
@@ -93,34 +74,36 @@ func (k *Key) valid() bool {
return true
}
// Equal reports whether two keys are equal. Two keys are equal if they are
// both nil, or if their kinds, IDs, names, namespaces and parents are equal.
func (k *Key) Equal(o *Key) bool {
for {
if k == nil || o == nil {
return k == o // if either is nil, both must be nil
}
if k.namespace != o.namespace || k.name != o.name || k.id != o.id || k.kind != o.kind {
if k.Namespace != o.Namespace || k.Name != o.Name || k.ID != o.ID || k.Kind != o.Kind {
return false
}
if k.parent == nil && o.parent == nil {
if k.Parent == nil && o.Parent == nil {
return true
}
k = k.parent
o = o.parent
k = k.Parent
o = o.Parent
}
}
// marshal marshals the key's string representation to the buffer.
func (k *Key) marshal(b *bytes.Buffer) {
if k.parent != nil {
k.parent.marshal(b)
if k.Parent != nil {
k.Parent.marshal(b)
}
b.WriteByte('/')
b.WriteString(k.kind)
b.WriteString(k.Kind)
b.WriteByte(',')
if k.name != "" {
b.WriteString(k.name)
if k.Name != "" {
b.WriteString(k.Name)
} else {
b.WriteString(strconv.FormatInt(k.id, 10))
b.WriteString(strconv.FormatInt(k.ID, 10))
}
}
@@ -150,11 +133,11 @@ func keyToGobKey(k *Key) *gobKey {
return nil
}
return &gobKey{
Kind: k.kind,
StringID: k.name,
IntID: k.id,
Parent: keyToGobKey(k.parent),
Namespace: k.namespace,
Kind: k.Kind,
StringID: k.Name,
IntID: k.ID,
Parent: keyToGobKey(k.Parent),
Namespace: k.Namespace,
}
}
@@ -163,14 +146,16 @@ func gobKeyToKey(gk *gobKey) *Key {
return nil
}
return &Key{
kind: gk.Kind,
name: gk.StringID,
id: gk.IntID,
parent: gobKeyToKey(gk.Parent),
namespace: gk.Namespace,
Kind: gk.Kind,
Name: gk.StringID,
ID: gk.IntID,
Parent: gobKeyToKey(gk.Parent),
Namespace: gk.Namespace,
}
}
// GobEncode marshals the key into a sequence of bytes
// using an encoding/gob.Encoder.
func (k *Key) GobEncode() ([]byte, error) {
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil {
@@ -179,6 +164,7 @@ func (k *Key) GobEncode() ([]byte, error) {
return buf.Bytes(), nil
}
// GobDecode unmarshals a sequence of bytes using an encoding/gob.Decoder.
func (k *Key) GobDecode(buf []byte) error {
gk := new(gobKey)
if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil {
@@ -188,10 +174,12 @@ func (k *Key) GobDecode(buf []byte) error {
return nil
}
// MarshalJSON marshals the key into JSON.
func (k *Key) MarshalJSON() ([]byte, error) {
return []byte(`"` + k.Encode() + `"`), nil
}
// UnmarshalJSON unmarshals a key JSON object into a Key.
func (k *Key) UnmarshalJSON(buf []byte) error {
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
return errors.New("datastore: bad JSON key")
@@ -238,29 +226,8 @@ func DecodeKey(encoded string) (*Key, error) {
return protoToKey(pKey)
}
// NewIncompleteKey creates a new incomplete key.
// kind cannot be empty.
func NewIncompleteKey(ctx context.Context, kind string, parent *Key) *Key {
return NewKey(ctx, kind, "", 0, parent)
}
// NewKey creates a new key.
// kind cannot be empty.
// At least one of name and id must be zero. If both are zero, the key returned
// is incomplete.
// parent must either be a complete key or nil.
func NewKey(ctx context.Context, kind, name string, id int64, parent *Key) *Key {
return &Key{
kind: kind,
name: name,
id: id,
parent: parent,
namespace: ctxNamespace(ctx),
}
}
// AllocateIDs accepts a slice of incomplete keys and returns a
// slice of complete keys that are guaranteed to be valid in the datastore
// slice of complete keys that are guaranteed to be valid in the datastore.
func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) {
if keys == nil {
return nil, nil
@@ -277,3 +244,37 @@ func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) {
return multiProtoToKey(resp.Keys)
}
// IncompleteKey creates a new incomplete key.
// The supplied kind cannot be empty.
// The namespace of the new key is empty.
func IncompleteKey(kind string, parent *Key) *Key {
return &Key{
Kind: kind,
Parent: parent,
}
}
// NameKey creates a new key with a name.
// The supplied kind cannot be empty.
// The supplied parent must either be a complete key or nil.
// The namespace of the new key is empty.
func NameKey(kind, name string, parent *Key) *Key {
return &Key{
Kind: kind,
Name: name,
Parent: parent,
}
}
// IDKey creates a new key with an ID.
// The supplied kind cannot be empty.
// The supplied parent must either be a complete key or nil.
// The namespace of the new key is empty.
func IDKey(kind string, id int64, parent *Key) *Key {
return &Key{
Kind: kind,
ID: id,
Parent: parent,
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,38 +19,9 @@ import (
"encoding/gob"
"encoding/json"
"testing"
"golang.org/x/net/context"
)
func TestNamespace(t *testing.T) {
c := context.Background()
k := NewIncompleteKey(c, "foo", nil)
if got, want := k.Namespace(), ""; got != want {
t.Errorf("No namespace, k.Namespace() = %q, want %q", got, want)
}
c = WithNamespace(c, "gopherspace")
k = NewIncompleteKey(c, "foo", nil)
if got, want := k.Namespace(), "gopherspace"; got != want {
t.Errorf("No namespace, k.Namespace() = %q, want %q", got, want)
}
}
func TestParent(t *testing.T) {
c := context.Background()
k := NewIncompleteKey(c, "foo", nil)
par := NewKey(c, "foomum", "", 1248, nil)
k.SetParent(par)
if got := k.Parent(); got != par {
t.Errorf("k.Parent() = %v; want %v", got, par)
}
}
func TestEqual(t *testing.T) {
c := context.Background()
cN := WithNamespace(c, "gopherspace")
testCases := []struct {
x, y *Key
equal bool
@@ -61,53 +32,53 @@ func TestEqual(t *testing.T) {
equal: true,
},
{
x: NewKey(c, "kindA", "", 0, nil),
y: NewIncompleteKey(c, "kindA", nil),
x: &Key{Kind: "kindA"},
y: &Key{Kind: "kindA"},
equal: true,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "nameA", 0, nil),
x: &Key{Kind: "kindA", Name: "nameA"},
y: &Key{Kind: "kindA", Name: "nameA"},
equal: true,
},
{
x: NewKey(cN, "kindA", "nameA", 0, nil),
y: NewKey(cN, "kindA", "nameA", 0, nil),
x: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"},
y: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"},
equal: true,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}},
y: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}},
equal: true,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindB", "nameA", 0, nil),
x: &Key{Kind: "kindA", Name: "nameA"},
y: &Key{Kind: "kindB", Name: "nameA"},
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "nameB", 0, nil),
x: &Key{Kind: "kindA", Name: "nameA"},
y: &Key{Kind: "kindA", Name: "nameB"},
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "", 1337, nil),
x: &Key{Kind: "kindA", Name: "nameA"},
y: &Key{Kind: "kindA", ID: 1337},
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(cN, "kindA", "nameA", 0, nil),
x: &Key{Kind: "kindA", Name: "nameA"},
y: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"},
equal: false,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, NewKey(c, "kindY", "nameX", 0, nil)),
x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}},
y: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindY", Name: "nameX"}},
equal: false,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, nil),
x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}},
y: &Key{Kind: "kindA", ID: 1337},
equal: false,
},
}
@@ -123,9 +94,6 @@ func TestEqual(t *testing.T) {
}
func TestEncoding(t *testing.T) {
c := context.Background()
cN := WithNamespace(c, "gopherspace")
testCases := []struct {
k *Key
valid bool
@@ -135,39 +103,39 @@ func TestEncoding(t *testing.T) {
valid: false,
},
{
k: NewKey(c, "", "", 0, nil),
k: &Key{},
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, nil),
k: &Key{Kind: "kindA"},
valid: true,
},
{
k: NewKey(cN, "kindA", "", 0, nil),
k: &Key{Kind: "kindA", Namespace: "gopherspace"},
valid: true,
},
{
k: NewKey(c, "kindA", "nameA", 0, nil),
k: &Key{Kind: "kindA", Name: "nameA"},
valid: true,
},
{
k: NewKey(c, "kindA", "", 1337, nil),
k: &Key{Kind: "kindA", ID: 1337},
valid: true,
},
{
k: NewKey(c, "kindA", "nameA", 1337, nil),
k: &Key{Kind: "kindA", Name: "nameA", ID: 1337},
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(c, "kindB", "nameB", 0, nil)),
k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB", Name: "nameB"}},
valid: true,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(c, "kindB", "", 0, nil)),
k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB"}},
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(cN, "kindB", "nameB", 0, nil)),
k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB", Name: "nameB", Namespace: "gopherspace"}},
valid: false,
},
}
@@ -225,7 +193,7 @@ func TestEncoding(t *testing.T) {
func TestInvalidKeyDecode(t *testing.T) {
// Check that decoding an invalid key returns an err and doesn't panic.
enc := NewKey(context.Background(), "Kind", "Foo", 0, nil).Encode()
enc := NameKey("Kind", "Foo", nil).Encode()
invalid := []string{
"",

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import (
"strings"
"time"
"cloud.google.com/go/internal/fields"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
@@ -28,7 +29,6 @@ var (
typeOfTime = reflect.TypeOf(time.Time{})
typeOfGeoPoint = reflect.TypeOf(GeoPoint{})
typeOfKeyPtr = reflect.TypeOf(&Key{})
typeOfEntityPtr = reflect.TypeOf(&Entity{})
)
// typeMismatchReason returns a string explaining why the property p could not
@@ -46,6 +46,8 @@ func typeMismatchReason(p Property, v reflect.Value) string {
entityType = "float"
case *Key:
entityType = "*datastore.Key"
case *Entity:
entityType = "*datastore.Entity"
case GeoPoint:
entityType = "GeoPoint"
case time.Time:
@@ -57,13 +59,17 @@ func typeMismatchReason(p Property, v reflect.Value) string {
return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type())
}
func overflowReason(x interface{}, v reflect.Value) string {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
type propertyLoader struct {
// m holds the number of times a substruct field like "Foo.Bar.Baz" has
// been seen so far. The map is constructed lazily.
m map[string]int
}
func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p Property, prev map[string]struct{}) string {
func (l *propertyLoader) load(codec fields.List, structValue reflect.Value, p Property, prev map[string]struct{}) string {
sl, ok := p.Value.([]interface{})
if !ok {
return l.loadOneElement(codec, structValue, p, prev)
@@ -80,45 +86,39 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
// loadOneElement loads the value of Property p into structValue based on the provided
// codec. codec is used to find the field in structValue into which p should be loaded.
// prev is the set of property names already seen for structValue.
func (l *propertyLoader) loadOneElement(codec *structCodec, structValue reflect.Value, p Property, prev map[string]struct{}) string {
func (l *propertyLoader) loadOneElement(codec fields.List, structValue reflect.Value, p Property, prev map[string]struct{}) string {
var sliceOk bool
var sliceIndex int
var v reflect.Value
name := p.Name
for name != "" {
// First we try to find a field with name matching
// the value of 'name' exactly.
decoder, ok := codec.fields[name]
if ok {
name = ""
} else {
// Now try for legacy flattened nested field (named eg. "A.B.C.D").
fieldNames := strings.Split(name, ".")
parent := name
child := ""
for len(fieldNames) > 0 {
var field *fields.Field
// Cut off the last field (delimited by ".") and find its parent
// in the codec.
// eg. for name "A.B.C.D", split off "A.B.C" and try to
// find a field in the codec with this name.
// Loop again with "A.B", etc.
for !ok {
i := strings.LastIndex(parent, ".")
if i < 0 {
return "no such struct field"
}
if i == len(name)-1 {
return "field name cannot end with '.'"
}
parent, child = name[:i], name[i+1:]
decoder, ok = codec.fields[parent]
// Start by trying to find a field with name. If none found,
// cut off the last field (delimited by ".") and find its parent
// in the codec.
// eg. for name "A.B.C.D", split off "A.B.C" and try to
// find a field in the codec with this name.
// Loop again with "A.B", etc.
for i := len(fieldNames); i > 0; i-- {
parent := strings.Join(fieldNames[:i], ".")
field = codec.Match(parent)
if field != nil {
fieldNames = fieldNames[i:]
break
}
name = child
}
v = initField(structValue, decoder.path)
// If we never found a matching field in the codec, return
// error message.
if field == nil {
return "no such struct field"
}
v = initField(structValue, field.Index)
if !v.IsValid() {
return "no such struct field"
}
@@ -126,13 +126,39 @@ func (l *propertyLoader) loadOneElement(codec *structCodec, structValue reflect.
return "cannot set struct field"
}
if decoder.structCodec != nil {
codec = decoder.structCodec
// If field implements PLS, we delegate loading to the PLS's Load early,
// and stop iterating through fields.
ok, err := plsFieldLoad(v, p, fieldNames)
if err != nil {
return err.Error()
}
if ok {
return ""
}
if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
codec, err = structCache.Fields(field.Type.Elem())
if err != nil {
return err.Error()
}
// Init value if its nil
if v.IsNil() {
v.Set(reflect.New(field.Type.Elem()))
}
structValue = v.Elem()
}
if field.Type.Kind() == reflect.Struct {
codec, err = structCache.Fields(field.Type)
if err != nil {
return err.Error()
}
structValue = v
}
// If the element is a slice, we need to accommodate it.
if v.Kind() == reflect.Slice {
if v.Kind() == reflect.Slice && v.Type() != typeOfByteSlice {
if l.m == nil {
l.m = make(map[string]int)
}
@@ -142,6 +168,23 @@ func (l *propertyLoader) loadOneElement(codec *structCodec, structValue reflect.
v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
}
structValue = v.Index(sliceIndex)
// If structValue implements PLS, we delegate loading to the PLS's
// Load early, and stop iterating through fields.
ok, err := plsFieldLoad(structValue, p, fieldNames)
if err != nil {
return err.Error()
}
if ok {
return ""
}
if structValue.Type().Kind() == reflect.Struct {
codec, err = structCache.Fields(structValue.Type())
if err != nil {
return err.Error()
}
}
sliceOk = true
}
}
@@ -174,8 +217,49 @@ func (l *propertyLoader) loadOneElement(codec *structCodec, structValue reflect.
return ""
}
// plsFieldLoad first tries to converts v's value to a PLS, then v's addressed
// value to a PLS. If neither succeeds, plsFieldLoad returns false for first return
// value. Otherwise, the first return value will be true.
// If v is successfully converted to a PLS, plsFieldLoad will then try to Load
// the property p into v (by way of the PLS's Load method).
//
// If the field v has been flattened, the Property's name must be altered
// before calling Load to reflect the field v.
// For example, if our original field name was "A.B.C.D",
// and at this point in iteration we had initialized the field
// corresponding to "A" and have moved into the struct, so that now
// v corresponds to the field named "B", then we want to let the
// PLS handle this field (B)'s subfields ("C", "D"),
// so we send the property to the PLS's Load, renamed to "C.D".
//
// If subfields are present, the field v has been flattened.
func plsFieldLoad(v reflect.Value, p Property, subfields []string) (ok bool, err error) {
vpls, err := plsForLoad(v)
if err != nil {
return false, err
}
if vpls == nil {
return false, nil
}
// If Entity, load properties as well as key.
if e, ok := p.Value.(*Entity); ok {
err = loadEntity(vpls, e)
return true, err
}
// If flattened, we must alter the property's name to reflect
// the field v.
if len(subfields) > 0 {
p.Name = strings.Join(subfields, ".")
}
return true, vpls.Load([]Property{p})
}
// setVal sets 'v' to the value of the Property 'p'.
func setVal(v reflect.Value, p Property) string {
func setVal(v reflect.Value, p Property) (s string) {
pValue := p.Value
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -184,7 +268,7 @@ func setVal(v reflect.Value, p Property) string {
return typeMismatchReason(p, v)
}
if v.OverflowInt(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
return overflowReason(x, v)
}
v.SetInt(x)
case reflect.Bool:
@@ -205,18 +289,58 @@ func setVal(v reflect.Value, p Property) string {
return typeMismatchReason(p, v)
}
if v.OverflowFloat(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
return overflowReason(x, v)
}
v.SetFloat(x)
case reflect.Ptr:
x, ok := pValue.(*Key)
if !ok && pValue != nil {
// v must be a pointer to either a Key, an Entity, or one of the supported basic types.
if v.Type() != typeOfKeyPtr && v.Type().Elem().Kind() != reflect.Struct && !isValidPointerType(v.Type().Elem()) {
return typeMismatchReason(p, v)
}
if _, ok := v.Interface().(*Key); !ok {
if pValue == nil {
// If v is populated already, set it to nil.
if !v.IsNil() {
v.Set(reflect.New(v.Type()).Elem())
}
return ""
}
if x, ok := p.Value.(*Key); ok {
if _, ok := v.Interface().(*Key); !ok {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
return ""
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
switch x := pValue.(type) {
case *Entity:
err := loadEntity(v.Interface(), x)
if err != nil {
return err.Error()
}
case int64:
if v.Elem().OverflowInt(x) {
return overflowReason(x, v.Elem())
}
v.Elem().SetInt(x)
case float64:
if v.Elem().OverflowFloat(x) {
return overflowReason(x, v.Elem())
}
v.Elem().SetFloat(x)
case bool:
v.Elem().SetBool(x)
case string:
v.Elem().SetString(x)
case GeoPoint, time.Time:
v.Elem().Set(reflect.ValueOf(x))
default:
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
case reflect.Struct:
switch v.Type() {
case typeOfTime:
@@ -236,20 +360,7 @@ func setVal(v reflect.Value, p Property) string {
if !ok {
return typeMismatchReason(p, v)
}
// Recursively load nested struct
pls, err := newStructPLS(v.Addr().Interface())
if err != nil {
return err.Error()
}
// if ent has a Key value and our struct has a Key field,
// load the Entity's Key value into the Key field on the struct.
if ent.Key != nil && pls.codec.keyField != -1 {
pls.v.Field(pls.codec.keyField).Set(reflect.ValueOf(ent.Key))
}
err = pls.Load(ent.Properties)
err := loadEntity(v.Addr().Interface(), ent)
if err != nil {
return err.Error()
}
@@ -285,16 +396,43 @@ func initField(val reflect.Value, index []int) reflect.Value {
return val.Field(index[len(index)-1])
}
// loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
func loadEntity(dst interface{}, src *pb.Entity) (err error) {
// loadEntityProto loads an EntityProto into PropertyLoadSaver or struct pointer.
func loadEntityProto(dst interface{}, src *pb.Entity) error {
ent, err := protoToEntity(src)
if err != nil {
return err
}
if e, ok := dst.(PropertyLoadSaver); ok {
return e.Load(ent.Properties)
return loadEntity(dst, ent)
}
func loadEntity(dst interface{}, ent *Entity) error {
if pls, ok := dst.(PropertyLoadSaver); ok {
err := pls.Load(ent.Properties)
if err != nil {
return err
}
if e, ok := dst.(KeyLoader); ok {
err = e.LoadKey(ent.Key)
}
return err
}
return LoadStruct(dst, ent.Properties)
return loadEntityToStruct(dst, ent)
}
func loadEntityToStruct(dst interface{}, ent *Entity) error {
pls, err := newStructPLS(dst)
if err != nil {
return err
}
// Try and load key.
keyField := pls.codec.Match(keyFieldName)
if keyField != nil && ent.Key != nil {
pls.v.FieldByIndex(keyField.Index).Set(reflect.ValueOf(ent.Key))
}
// Load properties.
return pls.Load(ent.Properties)
}
func (s structPLS) Load(props []Property) error {

View File

@@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All Rights Reserved.
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -8,7 +8,7 @@
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRNestedSimpleWithTagIES OR CONDITIONS OF NestedSimpleY KIND, either express or implied.
// 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.
@@ -17,7 +17,9 @@ package datastore
import (
"reflect"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
@@ -84,71 +86,71 @@ func TestLoadEntityNestedLegacy(t *testing.T) {
want interface{}
}{
{
"nested",
&pb.Entity{
desc: "nested",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{"two"}},
"A.I": {ValueType: &pb.Value_IntegerValue{2}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"A.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
&NestedSimple1{
want: &NestedSimple1{
A: Simple{I: 2},
X: "two",
},
},
{
"nested with tag",
&pb.Entity{
desc: "nested with tag",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"AA.II": {ValueType: &pb.Value_IntegerValue{2}},
"AA.II": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
&NestedSimpleWithTag{
want: &NestedSimpleWithTag{
A: SimpleWithTag{I: 2},
},
},
{
"nested with anonymous struct field",
&pb.Entity{
desc: "nested with anonymous struct field",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{"two"}},
"I": {ValueType: &pb.Value_IntegerValue{2}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
&NestedSimpleAnonymous{
want: &NestedSimpleAnonymous{
Simple: Simple{I: 2},
X: "two",
},
},
{
"nested with dotted field tag",
&pb.Entity{
desc: "nested with dotted field tag",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"A.B.B": {ValueType: &pb.Value_StringValue{"bb"}},
"A.B.B": {ValueType: &pb.Value_StringValue{StringValue: "bb"}},
},
},
&ABDotB{
want: &ABDotB{
A: BDotB{
B: "bb",
},
},
},
{
"nested with multiple anonymous fields",
&pb.Entity{
desc: "nested with multiple anonymous fields",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"S": {ValueType: &pb.Value_StringValue{"S"}},
"SS": {ValueType: &pb.Value_StringValue{"s"}},
"X": {ValueType: &pb.Value_StringValue{"s"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
"S": {ValueType: &pb.Value_StringValue{StringValue: "S"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "s"}},
},
},
&MultiAnonymous{
want: &MultiAnonymous{
Simple: Simple{I: 3},
SimpleTwoFields: SimpleTwoFields{S: "S", SS: "s"},
X: "s",
@@ -158,13 +160,13 @@ func TestLoadEntityNestedLegacy(t *testing.T) {
for _, tc := range testCases {
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
err := loadEntity(dst, tc.src)
err := loadEntityProto(dst, tc.src)
if err != nil {
t.Errorf("loadEntity: %s: %v", tc.desc, err)
t.Errorf("loadEntityProto: %s: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(tc.want, dst) {
if !testutil.Equal(tc.want, dst) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
}
}
@@ -193,71 +195,71 @@ func TestLoadEntityNested(t *testing.T) {
want interface{}
}{
{
"nested basic",
&pb.Entity{
desc: "nested basic",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
},
},
}},
"I": {ValueType: &pb.Value_IntegerValue{10}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
},
},
&NestedSimple{
want: &NestedSimple{
A: Simple{I: 3},
I: 10,
},
},
{
"nested with struct tags",
&pb.Entity{
desc: "nested with struct tags",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"AA": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"II": {ValueType: &pb.Value_IntegerValue{1}},
"II": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
},
},
}},
},
},
&NestedSimpleWithTag{
want: &NestedSimpleWithTag{
A: SimpleWithTag{I: 1},
},
},
{
"nested 2x",
&pb.Entity{
desc: "nested 2x",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"AA": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
},
},
}},
"I": {ValueType: &pb.Value_IntegerValue{1}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
},
},
}},
"A": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"S": {ValueType: &pb.Value_StringValue{"S"}},
"SS": {ValueType: &pb.Value_StringValue{"s"}},
"S": {ValueType: &pb.Value_StringValue{StringValue: "S"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}},
},
},
}},
"S": {ValueType: &pb.Value_StringValue{"SS"}},
"S": {ValueType: &pb.Value_StringValue{StringValue: "SS"}},
},
},
&NestedSimple2X{
want: &NestedSimple2X{
AA: NestedSimple{
A: Simple{I: 3},
I: 1,
@@ -267,36 +269,36 @@ func TestLoadEntityNested(t *testing.T) {
},
},
{
"nested anonymous",
&pb.Entity{
desc: "nested anonymous",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"X": {ValueType: &pb.Value_StringValue{"SomeX"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "SomeX"}},
},
},
&NestedSimpleAnonymous{
want: &NestedSimpleAnonymous{
Simple: Simple{I: 3},
X: "SomeX",
},
},
{
"nested simple with slice",
&pb.Entity{
desc: "nested simple with slice",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_ArrayValue{
&pb.ArrayValue{
[]*pb.Value{
ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
},
},
}},
{ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{4}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}},
},
},
}},
@@ -306,63 +308,63 @@ func TestLoadEntityNested(t *testing.T) {
},
},
&NestedSliceOfSimple{
A: []Simple{Simple{I: 3}, Simple{I: 4}},
want: &NestedSliceOfSimple{
A: []Simple{{I: 3}, {I: 4}},
},
},
{
"nested with multiple anonymous fields",
&pb.Entity{
desc: "nested with multiple anonymous fields",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{3}},
"S": {ValueType: &pb.Value_StringValue{"S"}},
"SS": {ValueType: &pb.Value_StringValue{"s"}},
"X": {ValueType: &pb.Value_StringValue{"ss"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
"S": {ValueType: &pb.Value_StringValue{StringValue: "S"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "ss"}},
},
},
&MultiAnonymous{
want: &MultiAnonymous{
Simple: Simple{I: 3},
SimpleTwoFields: SimpleTwoFields{S: "S", SS: "s"},
X: "ss",
},
},
{
"nested with dotted field tag",
&pb.Entity{
desc: "nested with dotted field tag",
src: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"B.B": {ValueType: &pb.Value_StringValue{"bb"}},
"B.B": {ValueType: &pb.Value_StringValue{StringValue: "bb"}},
},
},
}},
},
},
&ABDotB{
want: &ABDotB{
A: BDotB{
B: "bb",
},
},
},
{
"nested entity with key",
&pb.Entity{
desc: "nested entity with key",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Y": {ValueType: &pb.Value_StringValue{"yyy"}},
"Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
"N": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Key: keyToProto(testKey1a),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{"two"}},
"I": {ValueType: &pb.Value_IntegerValue{2}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
&NestedWithKey{
want: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
@@ -372,23 +374,23 @@ func TestLoadEntityNested(t *testing.T) {
},
},
{
"nested entity with invalid key",
&pb.Entity{
desc: "nested entity with invalid key",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Y": {ValueType: &pb.Value_StringValue{"yyy"}},
"Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
"N": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Key: keyToProto(invalidKey),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{"two"}},
"I": {ValueType: &pb.Value_IntegerValue{2}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
&NestedWithKey{
want: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
@@ -401,14 +403,507 @@ func TestLoadEntityNested(t *testing.T) {
for _, tc := range testCases {
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
err := loadEntity(dst, tc.src)
err := loadEntityProto(dst, tc.src)
if err != nil {
t.Errorf("loadEntity: %s: %v", tc.desc, err)
t.Errorf("loadEntityProto: %s: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(tc.want, dst) {
if !testutil.Equal(tc.want, dst) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
}
}
}
type NestedStructPtrs struct {
*SimpleTwoFields
Nest *SimpleTwoFields
TwiceNest *NestedSimple2
I int
}
type NestedSimple2 struct {
A *Simple
I int
}
func TestAlreadyPopulatedDst(t *testing.T) {
testCases := []struct {
desc string
src *pb.Entity
dst interface{}
want interface{}
}{
{
desc: "simple already populated, nil properties",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_NullValue{}},
},
},
dst: &Simple{
I: 12,
},
want: &Simple{},
},
{
desc: "nested structs already populated",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "world"}},
},
},
dst: &SimpleTwoFields{S: "hello" /* SS: "" */},
want: &SimpleTwoFields{S: "hello", SS: "world"},
},
{
desc: "nested structs already populated, pValues nil",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"S": {ValueType: &pb.Value_NullValue{}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "ss hello"}},
"Nest": {ValueType: &pb.Value_NullValue{}},
"TwiceNest": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_NullValue{}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
},
},
dst: &NestedStructPtrs{
&SimpleTwoFields{S: "hello" /* SS: "" */},
&SimpleTwoFields{ /* S: "" */ SS: "twice hello"},
&NestedSimple2{
A: &Simple{I: 2},
/* I: 0 */
},
0,
},
want: &NestedStructPtrs{
&SimpleTwoFields{ /* S: "" */ SS: "ss hello"},
nil,
&NestedSimple2{
/* A: nil, */
I: 2,
},
5,
},
},
}
for _, tc := range testCases {
err := loadEntityProto(tc.dst, tc.src)
if err != nil {
t.Errorf("loadEntityProto: %s: %v", tc.desc, err)
continue
}
if !testutil.Equal(tc.want, tc.dst) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, tc.dst, tc.want)
}
}
}
type PLS0 struct {
A string
}
func (p *PLS0) Load(props []Property) error {
for _, pp := range props {
if pp.Name == "A" {
p.A = pp.Value.(string)
}
}
return nil
}
func (p *PLS0) Save() (props []Property, err error) {
return []Property{{Name: "A", Value: p.A}}, nil
}
type KeyLoader1 struct {
A string
K *Key
}
func (kl *KeyLoader1) Load(props []Property) error {
for _, pp := range props {
if pp.Name == "A" {
kl.A = pp.Value.(string)
}
}
return nil
}
func (kl *KeyLoader1) Save() (props []Property, err error) {
return []Property{{Name: "A", Value: kl.A}}, nil
}
func (kl *KeyLoader1) LoadKey(k *Key) error {
kl.K = k
return nil
}
type KeyLoader2 struct {
B int
Key *Key
}
func (kl *KeyLoader2) Load(props []Property) error {
for _, pp := range props {
if pp.Name == "B" {
kl.B = int(pp.Value.(int64))
}
}
return nil
}
func (kl *KeyLoader2) Save() (props []Property, err error) {
return []Property{{Name: "B", Value: int64(kl.B)}}, nil
}
func (kl *KeyLoader2) LoadKey(k *Key) error {
kl.Key = k
return nil
}
type KeyLoader3 struct {
C bool
K *Key
}
func (kl *KeyLoader3) Load(props []Property) error {
for _, pp := range props {
if pp.Name == "C" {
kl.C = pp.Value.(bool)
}
}
return nil
}
func (kl *KeyLoader3) Save() (props []Property, err error) {
return []Property{{Name: "C", Value: kl.C}}, nil
}
func (kl *KeyLoader3) LoadKey(k *Key) error {
kl.K = k
return nil
}
type KeyLoader4 struct {
PLS0
K *Key
}
func (kl *KeyLoader4) LoadKey(k *Key) error {
kl.K = k
return nil
}
type NotKeyLoader struct {
A string
K *Key
}
func (p *NotKeyLoader) Load(props []Property) error {
for _, pp := range props {
if pp.Name == "A" {
p.A = pp.Value.(string)
}
}
return nil
}
func (p *NotKeyLoader) Save() (props []Property, err error) {
return []Property{{Name: "A", Value: p.A}}, nil
}
type NotPLSKeyLoader struct {
A string
K *Key `datastore:"__key__"`
}
type NestedKeyLoaders struct {
Two *KeyLoader2
Three []*KeyLoader3
Four *KeyLoader4
PLS *NotKeyLoader
}
func TestKeyLoader(t *testing.T) {
testCases := []struct {
desc string
src *pb.Entity
dst interface{}
want interface{}
}{
{
desc: "simple key loader",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
},
},
dst: &KeyLoader1{},
want: &KeyLoader1{
A: "hello",
K: testKey0,
},
},
{
desc: "simple key loader with unmatched properties",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "unmatched"}},
},
},
dst: &NotPLSKeyLoader{},
want: &NotPLSKeyLoader{
A: "hello",
K: testKey0,
},
},
{
desc: "embedded PLS key loader",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
},
},
dst: &KeyLoader4{},
want: &KeyLoader4{
PLS0: PLS0{A: "hello"},
K: testKey0,
},
},
{
desc: "nested key loaders",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Two": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"B": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}},
},
Key: keyToProto(testKey1a),
},
}},
"Three": {ValueType: &pb.Value_ArrayValue{
ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"C": {ValueType: &pb.Value_BooleanValue{BooleanValue: true}},
},
Key: keyToProto(testKey1b),
},
}},
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"C": {ValueType: &pb.Value_BooleanValue{BooleanValue: false}},
},
Key: keyToProto(testKey0),
},
}},
},
},
}},
"Four": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_StringValue{StringValue: "testing"}},
},
Key: keyToProto(testKey2a),
},
}},
"PLS": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_StringValue{StringValue: "something"}},
},
Key: keyToProto(testKey1a),
},
}},
},
},
dst: &NestedKeyLoaders{},
want: &NestedKeyLoaders{
Two: &KeyLoader2{B: 12, Key: testKey1a},
Three: []*KeyLoader3{
{
C: true,
K: testKey1b,
},
{
C: false,
K: testKey0,
},
},
Four: &KeyLoader4{
PLS0: PLS0{A: "testing"},
K: testKey2a,
},
PLS: &NotKeyLoader{A: "something"},
},
},
}
for _, tc := range testCases {
err := loadEntityProto(tc.dst, tc.src)
if err != nil {
// While loadEntityProto may return an error, if that error is
// ErrFieldMismatch, then there is still data in tc.dst to compare.
if _, ok := err.(*ErrFieldMismatch); !ok {
t.Errorf("loadEntityProto: %s: %v", tc.desc, err)
continue
}
}
if !testutil.Equal(tc.want, tc.dst) {
t.Errorf("%s: compare:\ngot: %+v\nwant: %+v", tc.desc, tc.dst, tc.want)
}
}
}
func TestLoadPointers(t *testing.T) {
for _, test := range []struct {
desc string
in []Property
want Pointers
}{
{
desc: "nil properties load as nil pointers",
in: []Property{
{Name: "Pi", Value: nil},
{Name: "Ps", Value: nil},
{Name: "Pb", Value: nil},
{Name: "Pf", Value: nil},
{Name: "Pg", Value: nil},
{Name: "Pt", Value: nil},
},
want: Pointers{},
},
{
desc: "missing properties load as nil pointers",
in: []Property(nil),
want: Pointers{},
},
{
desc: "non-nil properties load as the appropriate values",
in: []Property{
{Name: "Pi", Value: int64(1)},
{Name: "Ps", Value: "x"},
{Name: "Pb", Value: true},
{Name: "Pf", Value: 3.14},
{Name: "Pg", Value: GeoPoint{Lat: 1, Lng: 2}},
{Name: "Pt", Value: time.Unix(100, 0)},
},
want: func() Pointers {
p := populatedPointers()
*p.Pi = 1
*p.Ps = "x"
*p.Pb = true
*p.Pf = 3.14
*p.Pg = GeoPoint{Lat: 1, Lng: 2}
*p.Pt = time.Unix(100, 0)
return *p
}(),
},
} {
var got Pointers
if err := LoadStruct(&got, test.in); err != nil {
t.Fatalf("%s: %v", test.desc, err)
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s:\ngot %+v\nwant %+v", test.desc, got, test.want)
}
}
}
func TestLoadNonArrayIntoSlice(t *testing.T) {
// Loading a non-array value into a slice field results in a slice of size 1.
var got struct{ S []string }
if err := LoadStruct(&got, []Property{{Name: "S", Value: "x"}}); err != nil {
t.Fatal(err)
}
if want := []string{"x"}; !testutil.Equal(got.S, want) {
t.Errorf("got %#v, want %#v", got.S, want)
}
}
func TestLoadEmptyArrayIntoSlice(t *testing.T) {
// Loading an empty array into a slice field is a no-op.
var got = struct{ S []string }{[]string{"x"}}
if err := LoadStruct(&got, []Property{{Name: "S", Value: []interface{}{}}}); err != nil {
t.Fatal(err)
}
if want := []string{"x"}; !testutil.Equal(got.S, want) {
t.Errorf("got %#v, want %#v", got.S, want)
}
}
func TestLoadNull(t *testing.T) {
// Loading a Datastore Null into a basic type (int, float, etc.) results in a zero value.
// Loading a Null into a slice of basic type results in a slice of size 1 containing the zero value.
// (As expected from the behavior of slices and nulls with basic types.)
type S struct {
I int64
F float64
S string
B bool
A []string
}
got := S{
I: 1,
F: 1.0,
S: "1",
B: true,
A: []string{"X"},
}
want := S{A: []string{""}}
props := []Property{{Name: "I"}, {Name: "F"}, {Name: "S"}, {Name: "B"}, {Name: "A"}}
if err := LoadStruct(&got, props); err != nil {
t.Fatal(err)
}
if !testutil.Equal(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
// Loading a Null into a pointer to struct field results in a nil field.
got2 := struct{ X *S }{X: &S{}}
if err := LoadStruct(&got2, []Property{{Name: "X"}}); err != nil {
t.Fatal(err)
}
if got2.X != nil {
t.Errorf("got %v, want nil", got2.X)
}
// Loading a Null into a struct field is an error.
got3 := struct{ X S }{}
err := LoadStruct(&got3, []Property{{Name: "X"}})
if err == nil {
t.Error("got nil, want error")
}
}
// var got2 struct{ S []Pet }
// if err := LoadStruct(&got2, []Property{{Name: "S", Value: nil}}); err != nil {
// t.Fatal(err)
// }
// }

129
vendor/cloud.google.com/go/datastore/mutation.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
// Copyright 2018 Google LLC
//
// 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 datastore
import (
"fmt"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
// A Mutation represents a change to a Datastore entity.
type Mutation struct {
key *Key // needed for transaction PendingKeys and to dedup deletions
mut *pb.Mutation
err error
}
func (m *Mutation) isDelete() bool {
_, ok := m.mut.Operation.(*pb.Mutation_Delete)
return ok
}
// NewInsert creates a mutation that will save the entity src into the datastore with
// key k, returning an error if k already exists.
// See Client.Put for valid values of src.
func NewInsert(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Insert{Insert: p}},
}
}
// NewUpsert creates a mutation that saves the entity src into the datastore with key
// k, whether or not k exists. See Client.Put for valid values of src.
func NewUpsert(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: p}},
}
}
// NewUpdate creates a mutation that replaces the entity in the datastore with key k,
// returning an error if k does not exist. See Client.Put for valid values of src.
func NewUpdate(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
if k.Incomplete() {
return &Mutation{err: fmt.Errorf("datastore: can't update the incomplete key: %v", k)}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Update{Update: p}},
}
}
// NewDelete creates a mutation that deletes the entity with key k.
func NewDelete(k *Key) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
if k.Incomplete() {
return &Mutation{err: fmt.Errorf("datastore: can't delete the incomplete key: %v", k)}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Delete{Delete: keyToProto(k)}},
}
}
func mutationProtos(muts []*Mutation) ([]*pb.Mutation, error) {
// If any of the mutations have errors, collect and return them.
var merr MultiError
for i, m := range muts {
if m.err != nil {
if merr == nil {
merr = make(MultiError, len(muts))
}
merr[i] = m.err
}
}
if merr != nil {
return nil, merr
}
var protos []*pb.Mutation
// Collect protos. Remove duplicate deletions (see deleteMutations).
seen := map[string]bool{}
for _, m := range muts {
if m.isDelete() {
ks := m.key.String()
if seen[ks] {
continue
}
seen[ks] = true
}
protos = append(protos, m.mut)
}
return protos, nil
}

150
vendor/cloud.google.com/go/datastore/mutation_test.go generated vendored Normal file
View File

@@ -0,0 +1,150 @@
// Copyright 2018 Google LLC
//
// 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 datastore
import (
"testing"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestMutationProtos(t *testing.T) {
var keys []*Key
for i := 1; i <= 4; i++ {
k := IDKey("kind", int64(i), nil)
keys = append(keys, k)
}
entity := &PropertyList{{Name: "n", Value: "v"}}
entityForKey := func(k *Key) *pb.Entity {
return &pb.Entity{
Key: keyToProto(k),
Properties: map[string]*pb.Value{
"n": {ValueType: &pb.Value_StringValue{StringValue: "v"}},
},
}
}
for _, test := range []struct {
desc string
in []*Mutation
want []*pb.Mutation
}{
{
desc: "nil",
in: nil,
want: nil,
},
{
desc: "empty",
in: []*Mutation{},
want: nil,
},
{
desc: "various",
in: []*Mutation{
NewInsert(keys[0], entity),
NewUpsert(keys[1], entity),
NewUpdate(keys[2], entity),
NewDelete(keys[3]),
},
want: []*pb.Mutation{
{Operation: &pb.Mutation_Insert{Insert: entityForKey(keys[0])}},
{Operation: &pb.Mutation_Upsert{Upsert: entityForKey(keys[1])}},
{Operation: &pb.Mutation_Update{Update: entityForKey(keys[2])}},
{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[3])}},
},
},
{
desc: "duplicate deletes",
in: []*Mutation{
NewDelete(keys[0]),
NewInsert(keys[1], entity),
NewDelete(keys[0]),
NewDelete(keys[2]),
NewDelete(keys[0]),
},
want: []*pb.Mutation{
{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[0])}},
{Operation: &pb.Mutation_Insert{Insert: entityForKey(keys[1])}},
{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[2])}},
},
},
} {
got, err := mutationProtos(test.in)
if err != nil {
t.Errorf("%s: %v", test.desc, err)
continue
}
if diff := testutil.Diff(got, test.want); diff != "" {
t.Errorf("%s: %s", test.desc, diff)
}
}
}
func TestMutationProtosErrors(t *testing.T) {
entity := &PropertyList{{Name: "n", Value: "v"}}
k := IDKey("kind", 1, nil)
ik := IncompleteKey("kind", nil)
for _, test := range []struct {
desc string
in []*Mutation
want []int // non-nil indexes of MultiError
}{
{
desc: "invalid key",
in: []*Mutation{
NewInsert(nil, entity),
NewUpdate(nil, entity),
NewUpsert(nil, entity),
NewDelete(nil),
},
want: []int{0, 1, 2, 3},
},
{
desc: "incomplete key",
in: []*Mutation{
NewInsert(ik, entity),
NewUpdate(ik, entity),
NewUpsert(ik, entity),
NewDelete(ik),
},
want: []int{1, 3},
},
{
desc: "bad entity",
in: []*Mutation{
NewInsert(k, 1),
NewUpdate(k, 2),
NewUpsert(k, 3),
},
want: []int{0, 1, 2},
},
} {
_, err := mutationProtos(test.in)
if err == nil {
t.Errorf("%s: got nil, want error", test.desc)
continue
}
var got []int
for i, err := range err.(MultiError) {
if err != nil {
got = append(got, i)
}
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s: got errors at %v, want at %v", test.desc, got, test.want)
}
}
}

43
vendor/cloud.google.com/go/datastore/oc_test.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2018 Google LLC
//
// 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 datastore
import (
"context"
"testing"
"cloud.google.com/go/internal/testutil"
)
func TestOCTracing(t *testing.T) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
te := testutil.NewTestExporter()
defer te.Unregister()
type SomeValue struct {
S string
}
_, err := client.Put(ctx, IncompleteKey("SomeKey", nil), &SomeValue{"foo"})
if err != nil {
t.Fatalf("client.Put: %v", err)
}
if len(te.Spans) == 0 {
t.Fatalf("Expected some span to be created, but got %d", 0)
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,16 +18,14 @@ import (
"fmt"
"reflect"
"strings"
"sync"
"unicode"
"cloud.google.com/go/internal/fields"
)
// Entities with more than this many indexed properties will not be saved.
const maxIndexedProperties = 20000
// []byte fields more than 1 megabyte long will not be loaded or saved.
const maxBlobLen = 1 << 20
// Property is a name/value pair plus some metadata. A datastore entity's
// contents are loaded and saved as a sequence of Properties. Each property
// name must be unique within an entity.
@@ -40,7 +38,7 @@ type Property struct {
// - string
// - float64
// - *Key
// - time.Time
// - time.Time (retrieved as local time)
// - GeoPoint
// - []byte (up to 1 megabyte in length)
// - *Entity (representing a nested struct)
@@ -81,6 +79,14 @@ type PropertyLoadSaver interface {
Save() ([]Property, error)
}
// KeyLoader can store a Key.
type KeyLoader interface {
// PropertyLoadSaver is embedded because a KeyLoader
// must also always implement PropertyLoadSaver.
PropertyLoadSaver
LoadKey(k *Key) error
}
// PropertyList converts a []Property to implement PropertyLoadSaver.
type PropertyList []Property
@@ -128,164 +134,113 @@ func validPropertyName(name string) bool {
return true
}
// structCodec describes how to convert a struct to and from a sequence of
// properties.
type structCodec struct {
// fields gives the field codec for the structTag with the given name.
fields map[string]fieldCodec
// hasSlice is whether a struct or any of its nested or embedded structs
// has a slice-typed field (other than []byte).
hasSlice bool
// keyField is the index of a *Key field with structTag __key__.
// This field is not relevant for the top level struct, only for
// nested structs.
keyField int
// complete is whether the structCodec is complete. An incomplete
// structCodec may be encountered when walking a recursive struct.
complete bool
}
// fieldCodec is a struct field's index and, if that struct field's type is
// itself a struct, that substruct's structCodec.
type fieldCodec struct {
// path is the index path to the field
path []int
noIndex bool
// structCodec is the codec fot the struct field at index 'path',
// or nil if the field is not a struct.
structCodec *structCodec
}
// structCodecs collects the structCodecs that have already been calculated.
var (
structCodecsMutex sync.Mutex
structCodecs = make(map[reflect.Type]*structCodec)
)
// getStructCodec returns the structCodec for the given struct type.
func getStructCodec(t reflect.Type) (*structCodec, error) {
structCodecsMutex.Lock()
defer structCodecsMutex.Unlock()
return getStructCodecLocked(t)
}
// getStructCodecLocked implements getStructCodec. The structCodecsMutex must
// be held when calling this function.
func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
c, ok := structCodecs[t]
if ok {
return c, nil
// parseTag interprets datastore struct field tags
func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
s := t.Get("datastore")
parts := strings.Split(s, ",")
if parts[0] == "-" && len(parts) == 1 {
return "", false, nil, nil
}
c = &structCodec{
fields: make(map[string]fieldCodec),
// We initialize keyField to -1 so that the zero-value is not
// misinterpreted as index 0.
keyField: -1,
if parts[0] != "" && !validPropertyName(parts[0]) {
err = fmt.Errorf("datastore: struct tag has invalid property name: %q", parts[0])
return "", false, nil, err
}
// Add c to the structCodecs map before we are sure it is good. If t is
// a recursive type, it needs to find the incomplete entry for itself in
// the map.
structCodecs[t] = c
defer func() {
if retErr != nil {
delete(structCodecs, t)
var opts saveOpts
if len(parts) > 1 {
for _, p := range parts[1:] {
switch p {
case "flatten":
opts.flatten = true
case "omitempty":
opts.omitEmpty = true
case "noindex":
opts.noIndex = true
default:
err = fmt.Errorf("datastore: struct tag has invalid option: %q", p)
return "", false, nil, err
}
}
}()
other = opts
}
return parts[0], true, other, nil
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
// Skip unexported fields.
// Note that if f is an anonymous, unexported struct field,
// we will not promote its fields. We will skip f entirely.
if f.PkgPath != "" {
continue
func validateType(t reflect.Type) error {
if t.Kind() != reflect.Struct {
return fmt.Errorf("datastore: validate called with non-struct type %s", t)
}
return validateChildType(t, "", false, false, map[reflect.Type]bool{})
}
// validateChildType is a recursion helper func for validateType
func validateChildType(t reflect.Type, fieldName string, flatten, prevSlice bool, prevTypes map[reflect.Type]bool) error {
if prevTypes[t] {
return nil
}
prevTypes[t] = true
switch t.Kind() {
case reflect.Slice:
if flatten && prevSlice {
return fmt.Errorf("datastore: flattening nested structs leads to a slice of slices: field %q", fieldName)
}
return validateChildType(t.Elem(), fieldName, flatten, true, prevTypes)
case reflect.Struct:
if t == typeOfTime || t == typeOfGeoPoint {
return nil
}
name, opts := f.Tag.Get("datastore"), ""
if i := strings.Index(name, ","); i != -1 {
name, opts = name[:i], name[i+1:]
}
switch {
case name == "":
if !f.Anonymous {
name = f.Name
}
case name == "-":
continue
case name == "__key__":
if f.Type != typeOfKeyPtr {
return nil, fmt.Errorf("datastore: __key__ field on struct %v is not a *datastore.Key", t)
}
c.keyField = i
continue
case !validPropertyName(name):
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
substructType, fIsSlice := reflect.Type(nil), false
switch f.Type.Kind() {
case reflect.Struct:
substructType = f.Type
case reflect.Slice:
if f.Type.Elem().Kind() == reflect.Struct {
substructType = f.Type.Elem()
}
fIsSlice = f.Type != typeOfByteSlice
c.hasSlice = c.hasSlice || fIsSlice
}
var sub *structCodec
if substructType != nil && substructType != typeOfTime && substructType != typeOfGeoPoint {
var err error
sub, err = getStructCodecLocked(substructType)
if err != nil {
return nil, err
}
if !sub.complete {
return nil, fmt.Errorf("datastore: recursive struct: field %q", f.Name)
}
if fIsSlice && sub.hasSlice {
return nil, fmt.Errorf(
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
}
c.hasSlice = c.hasSlice || sub.hasSlice
// If name is empty at this point, f is an anonymous struct field.
// In this case, we promote the substruct's fields up to this level
// in the linked list of struct codecs.
if name == "" {
for subname, subfield := range sub.fields {
if _, ok := c.fields[subname]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", subname)
}
c.fields[subname] = fieldCodec{
path: append([]int{i}, subfield.path...),
noIndex: subfield.noIndex || opts == "noindex",
structCodec: subfield.structCodec,
}
}
// If a named field is unexported, ignore it. An anonymous
// unexported field is processed, because it may contain
// exported fields, which are visible.
exported := (f.PkgPath == "")
if !exported && !f.Anonymous {
continue
}
}
if _, ok := c.fields[name]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
_, keep, other, err := parseTag(f.Tag)
// Handle error from parseTag now instead of later (in cache.Fields call).
if err != nil {
return err
}
if !keep {
continue
}
if other != nil {
opts := other.(saveOpts)
flatten = flatten || opts.flatten
}
if err := validateChildType(f.Type, f.Name, flatten, prevSlice, prevTypes); err != nil {
return err
}
}
c.fields[name] = fieldCodec{
path: []int{i},
noIndex: opts == "noindex",
structCodec: sub,
case reflect.Ptr:
if t == typeOfKeyPtr {
return nil
}
return validateChildType(t.Elem(), fieldName, flatten, prevSlice, prevTypes)
}
c.complete = true
return c, nil
return 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 == typeOfTime || t == typeOfGeoPoint
}
// structCache collects the structs whose fields have already been calculated.
var structCache = fields.NewCache(parseTag, validateType, isLeafType)
// structPLS adapts a struct to be a PropertyLoadSaver.
type structPLS struct {
v reflect.Value
codec *structCodec
codec fields.List
}
// newStructPLS returns a structPLS, which implements the
@@ -296,15 +251,20 @@ func newStructPLS(p interface{}) (*structPLS, error) {
return nil, ErrInvalidEntityType
}
v = v.Elem()
codec, err := getStructCodec(v.Type())
f, err := structCache.Fields(v.Type())
if err != nil {
return nil, err
}
return &structPLS{v, codec}, nil
return &structPLS{v, f}, nil
}
// LoadStruct loads the properties from p to dst.
// dst must be a struct pointer.
//
// The values of dst's unmatched struct fields are not modified,
// and matching slice-typed fields are not reset before appending to
// them. In particular, it is recommended to pass a pointer to a zero
// valued struct on each LoadStruct call.
func LoadStruct(dst interface{}, p []Property) error {
x, err := newStructPLS(dst)
if err != nil {
@@ -322,3 +282,58 @@ func SaveStruct(src interface{}) ([]Property, error) {
}
return x.Save()
}
// plsForLoad tries to convert v to a PropertyLoadSaver.
// If successful, plsForLoad returns a settable v as a PropertyLoadSaver.
//
// plsForLoad is intended to be used with nested struct fields which
// may implement PropertyLoadSaver.
//
// v must be settable.
func plsForLoad(v reflect.Value) (PropertyLoadSaver, error) {
var nilPtr bool
if v.Kind() == reflect.Ptr && v.IsNil() {
nilPtr = true
v.Set(reflect.New(v.Type().Elem()))
}
vpls, err := pls(v)
if nilPtr && (vpls == nil || err != nil) {
// unset v
v.Set(reflect.Zero(v.Type()))
}
return vpls, err
}
// plsForSave tries to convert v to a PropertyLoadSaver.
// If successful, plsForSave returns v as a PropertyLoadSaver.
//
// plsForSave is intended to be used with nested struct fields which
// may implement PropertyLoadSaver.
//
// v must be settable.
func plsForSave(v reflect.Value) (PropertyLoadSaver, error) {
switch v.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan, reflect.Func:
// If v is nil, return early. v contains no data to save.
if v.IsNil() {
return nil, nil
}
}
return pls(v)
}
func pls(v reflect.Value) (PropertyLoadSaver, error) {
if v.Kind() != reflect.Ptr {
if _, ok := v.Interface().(PropertyLoadSaver); ok {
return nil, fmt.Errorf("datastore: PropertyLoadSaver methods must be implemented on a pointer to %T", v.Interface())
}
v = v.Addr()
}
vpls, _ := v.Interface().(PropertyLoadSaver)
return vpls, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
package datastore
import (
"context"
"encoding/base64"
"errors"
"fmt"
@@ -23,8 +24,9 @@ import (
"strconv"
"strings"
"cloud.google.com/go/internal/trace"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
@@ -93,13 +95,16 @@ type Query struct {
order []order
projection []string
distinct bool
keysOnly bool
eventual bool
limit int32
offset int32
start []byte
end []byte
distinct bool
distinctOn []string
keysOnly bool
eventual bool
limit int32
offset int32
start []byte
end []byte
namespace string
trans *Transaction
@@ -141,6 +146,17 @@ func (q *Query) EventualConsistency() *Query {
return q
}
// Namespace returns a derivative query that is associated with the given
// namespace.
//
// A namespace may be used to partition data for multi-tenant applications.
// For details, see https://cloud.google.com/datastore/docs/concepts/multitenancy.
func (q *Query) Namespace(ns string) *Query {
q = q.clone()
q.namespace = ns
return q
}
// Transaction returns a derivative query that is associated with the given
// transaction.
//
@@ -248,13 +264,23 @@ func (q *Query) Project(fieldNames ...string) *Query {
// Distinct returns a derivative query that yields de-duplicated entities with
// respect to the set of projected fields. It is only used for projection
// queries.
// queries. Distinct cannot be used with DistinctOn.
func (q *Query) Distinct() *Query {
q = q.clone()
q.distinct = true
return q
}
// DistinctOn returns a derivative query that yields de-duplicated entities with
// respect to the set of the specified fields. It is only used for projection
// queries. The field list should be a subset of the projected field list.
// DistinctOn cannot be used with Distinct.
func (q *Query) DistinctOn(fieldNames ...string) *Query {
q = q.clone()
q.distinctOn = fieldNames
return q
}
// KeysOnly returns a derivative query that yields only keys, not keys and
// entities. It cannot be used with projection queries.
func (q *Query) KeysOnly() *Query {
@@ -310,6 +336,9 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
if len(q.projection) != 0 && q.keysOnly {
return errors.New("datastore: query cannot both project and be keys-only")
}
if len(q.distinctOn) != 0 && q.distinct {
return errors.New("datastore: query cannot be both distinct and distinct-on")
}
dst := &pb.Query{}
if q.kind != "" {
dst.Kind = []*pb.KindExpression{{Name: q.kind}}
@@ -319,6 +348,10 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
dst.Projection = append(dst.Projection, &pb.Projection{Property: &pb.PropertyReference{Name: propertyName}})
}
for _, propertyName := range q.distinctOn {
dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName})
}
if q.distinct {
for _, propertyName := range q.projection {
dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName})
@@ -348,23 +381,23 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
Value: v,
}
filters = append(filters, &pb.Filter{
FilterType: &pb.Filter_PropertyFilter{xf},
FilterType: &pb.Filter_PropertyFilter{PropertyFilter: xf},
})
}
if q.ancestor != nil {
filters = append(filters, &pb.Filter{
FilterType: &pb.Filter_PropertyFilter{&pb.PropertyFilter{
Property: &pb.PropertyReference{Name: "__key__"},
FilterType: &pb.Filter_PropertyFilter{PropertyFilter: &pb.PropertyFilter{
Property: &pb.PropertyReference{Name: keyFieldName},
Op: pb.PropertyFilter_HAS_ANCESTOR,
Value: &pb.Value{ValueType: &pb.Value_KeyValue{keyToProto(q.ancestor)}},
Value: &pb.Value{ValueType: &pb.Value_KeyValue{KeyValue: keyToProto(q.ancestor)}},
}}})
}
if len(filters) == 1 {
dst.Filter = filters[0]
} else if len(filters) > 1 {
dst.Filter = &pb.Filter{FilterType: &pb.Filter_CompositeFilter{&pb.CompositeFilter{
dst.Filter = &pb.Filter{FilterType: &pb.Filter_CompositeFilter{CompositeFilter: &pb.CompositeFilter{
Op: pb.CompositeFilter_AND,
Filters: filters,
}}}
@@ -381,7 +414,7 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
dst.Order = append(dst.Order, xo)
}
if q.limit >= 0 {
dst.Limit = &wrapperspb.Int32Value{q.limit}
dst.Limit = &wrapperspb.Int32Value{Value: q.limit}
}
dst.Offset = q.offset
dst.StartCursor = q.start
@@ -395,25 +428,28 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
return errors.New("datastore: cannot use EventualConsistency query in a transaction")
}
req.ReadOptions = &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{t.id},
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
}
}
if q.eventual {
req.ReadOptions = &pb.ReadOptions{&pb.ReadOptions_ReadConsistency_{pb.ReadOptions_EVENTUAL}}
req.ReadOptions = &pb.ReadOptions{ConsistencyType: &pb.ReadOptions_ReadConsistency_{ReadConsistency: pb.ReadOptions_EVENTUAL}}
}
req.QueryType = &pb.RunQueryRequest_Query{dst}
req.QueryType = &pb.RunQueryRequest_Query{Query: dst}
return nil
}
// Count returns the number of results for the given query.
//
// The running time and number of API calls made by Count scale linearly with
// with the sum of the query's offset and limit. Unless the result count is
// the sum of the query's offset and limit. Unless the result count is
// expected to be small, it is best to specify a limit; otherwise Count will
// continue until it finishes counting or the provided context expires.
func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
func (c *Client) Count(ctx context.Context, q *Query) (n int, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Count")
defer func() { trace.EndSpan(ctx, err) }()
// Check that the query is well-formed.
if q.err != nil {
return 0, q.err
@@ -427,10 +463,9 @@ func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
// Create an iterator and use it to walk through the batches of results
// directly.
it := c.Run(ctx, newQ)
n := 0
for {
err := it.nextBatch()
if err == Done {
if err == iterator.Done {
return n, nil
}
if err != nil {
@@ -460,7 +495,10 @@ func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
// expected to be small, it is best to specify a limit; otherwise GetAll will
// continue until it finishes collecting results or the provided context
// expires.
func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key, error) {
func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) (keys []*Key, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.GetAll")
defer func() { trace.EndSpan(ctx, err) }()
var (
dv reflect.Value
mat multiArgType
@@ -479,10 +517,9 @@ func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key,
}
}
var keys []*Key
for t := c.Run(ctx, q); ; {
k, e, err := t.next()
if err == Done {
if err == iterator.Done {
break
}
if err != nil {
@@ -506,7 +543,7 @@ func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key,
x := reflect.MakeMap(elemType)
ev.Elem().Set(x)
}
if err = loadEntity(ev.Interface(), e); err != nil {
if err = loadEntityProto(ev.Interface(), e); err != nil {
if _, ok := err.(*ErrFieldMismatch); ok {
// We continue loading entities even in the face of field mismatch errors.
// If we encounter any other error, that other error is returned. Otherwise,
@@ -543,11 +580,13 @@ func (c *Client) Run(ctx context.Context, q *Query) *Iterator {
ProjectId: c.dataset,
},
}
if ns := ctxNamespace(ctx); ns != "" {
if q.namespace != "" {
t.req.PartitionId = &pb.PartitionId{
NamespaceId: ns,
NamespaceId: q.namespace,
}
}
if err := q.toProto(t.req); err != nil {
t.err = err
}
@@ -583,22 +622,19 @@ type Iterator struct {
entityCursor []byte
}
// Done is returned when a query iteration has completed.
var Done = errors.New("datastore: query has no more results")
// Next returns the key of the next result. When there are no more results,
// Done is returned as the error.
// iterator.Done is returned as the error.
//
// If the query is not keys only and dst is non-nil, it also loads the entity
// stored for that key into the struct pointer or PropertyLoadSaver dst, with
// the same semantics and possible errors as for the Get function.
func (t *Iterator) Next(dst interface{}) (*Key, error) {
func (t *Iterator) Next(dst interface{}) (k *Key, err error) {
k, e, err := t.next()
if err != nil {
return nil, err
}
if dst != nil && !t.keysOnly {
err = loadEntity(dst, e)
err = loadEntityProto(dst, e)
}
return k, err
}
@@ -632,8 +668,12 @@ func (t *Iterator) next() (*Key, *pb.Entity, error) {
// nextBatch makes a single call to the server for a batch of results.
func (t *Iterator) nextBatch() error {
if t.err != nil {
return t.err
}
if t.limit == 0 {
return Done // Short-circuits the zero-item response.
return iterator.Done // Short-circuits the zero-item response.
}
// Adjust the query with the latest start cursor, limit and offset.
@@ -641,7 +681,7 @@ func (t *Iterator) nextBatch() error {
q.StartCursor = t.pageCursor
q.Offset = t.offset
if t.limit >= 0 {
q.Limit = &wrapperspb.Int32Value{t.limit}
q.Limit = &wrapperspb.Int32Value{Value: t.limit}
} else {
q.Limit = nil
}
@@ -695,13 +735,16 @@ func (t *Iterator) nextBatch() error {
}
// Cursor returns a cursor for the iterator's current location.
func (t *Iterator) Cursor() (Cursor, error) {
func (t *Iterator) Cursor() (c Cursor, err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Query.Cursor")
defer func() { trace.EndSpan(t.ctx, err) }()
// If there is still an offset, we need to the skip those results first.
for t.err == nil && t.offset > 0 {
t.err = t.nextBatch()
}
if t.err != nil && t.err != Done {
if t.err != nil && t.err != iterator.Done {
return Cursor{}, t.err
}
@@ -727,7 +770,7 @@ func (c Cursor) String() string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=")
}
// Decode decodes a cursor from its base-64 string representation.
// DecodeCursor decodes a cursor from its base-64 string representation.
func DecodeCursor(s string) (Cursor, error) {
if s == "" {
return Cursor{}, nil

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,14 +15,16 @@
package datastore
import (
"context"
"errors"
"fmt"
"reflect"
"sort"
"testing"
"cloud.google.com/go/internal/testutil"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"github.com/google/go-cmp/cmp"
pb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
)
@@ -32,7 +34,7 @@ var (
Path: []*pb.Key_PathElement{
{
Kind: "Gopher",
IdType: &pb.Key_PathElement_Id{6},
IdType: &pb.Key_PathElement_Id{Id: 6},
},
},
}
@@ -40,11 +42,11 @@ var (
Path: []*pb.Key_PathElement{
{
Kind: "Gopher",
IdType: &pb.Key_PathElement_Id{6},
IdType: &pb.Key_PathElement_Id{Id: 6},
},
{
Kind: "Gopher",
IdType: &pb.Key_PathElement_Id{8},
IdType: &pb.Key_PathElement_Id{Id: 8},
},
},
}
@@ -66,7 +68,7 @@ func (c *fakeClient) Commit(_ context.Context, req *pb.CommitRequest, _ ...grpc.
func fakeRunQuery(in *pb.RunQueryRequest) (*pb.RunQueryResponse, error) {
expectedIn := &pb.RunQueryRequest{
QueryType: &pb.RunQueryRequest_Query{&pb.Query{
QueryType: &pb.RunQueryRequest_Query{Query: &pb.Query{
Kind: []*pb.KindExpression{{Name: "Gopher"}},
}},
}
@@ -82,8 +84,8 @@ func fakeRunQuery(in *pb.RunQueryRequest) (*pb.RunQueryResponse, error) {
Entity: &pb.Entity{
Key: key1,
Properties: map[string]*pb.Value{
"Name": {ValueType: &pb.Value_StringValue{"George"}},
"Height": {ValueType: &pb.Value_IntegerValue{32}},
"Name": {ValueType: &pb.Value_StringValue{StringValue: "George"}},
"Height": {ValueType: &pb.Value_IntegerValue{IntegerValue: 32}},
},
},
},
@@ -91,7 +93,7 @@ func fakeRunQuery(in *pb.RunQueryRequest) (*pb.RunQueryResponse, error) {
Entity: &pb.Entity{
Key: key2,
Properties: map[string]*pb.Value{
"Name": {ValueType: &pb.Value_StringValue{"Rufus"}},
"Name": {ValueType: &pb.Value_StringValue{StringValue: "Rufus"}},
// No height for Rufus.
},
},
@@ -311,10 +313,10 @@ func TestSimpleQuery(t *testing.T) {
continue
}
key1 := NewKey(ctx, "Gopher", "", 6, nil)
key1 := IDKey("Gopher", 6, nil)
expectedKeys := []*Key{
key1,
NewKey(ctx, "Gopher", "", 8, key1),
IDKey("Gopher", 8, key1),
}
if l1, l2 := len(keys), len(expectedKeys); l1 != l2 {
t.Errorf("dst type %T: got %d keys, want %d keys", tc.dst, l1, l2)
@@ -334,7 +336,7 @@ func TestSimpleQuery(t *testing.T) {
}
}
if !reflect.DeepEqual(tc.dst, tc.want) {
if !testutil.Equal(tc.dst, tc.want) {
t.Errorf("dst type %T: Entities\ngot %+v\nwant %+v", tc.dst, tc.dst, tc.want)
continue
}
@@ -344,10 +346,10 @@ func TestSimpleQuery(t *testing.T) {
// keysEqual is like (*Key).Equal, but ignores the App ID.
func keysEqual(a, b *Key) bool {
for a != nil && b != nil {
if a.Kind() != b.Kind() || a.Name() != b.Name() || a.ID() != b.ID() {
if a.Kind != b.Kind || a.Name != b.Name || a.ID != b.ID {
return false
}
a, b = a.Parent(), b.Parent()
a, b = a.Parent, b.Parent
}
return a == b
}
@@ -357,10 +359,10 @@ func TestQueriesAreImmutable(t *testing.T) {
q0 := NewQuery("foo")
q1 := NewQuery("foo")
q2 := q1.Offset(2)
if !reflect.DeepEqual(q0, q1) {
if !testutil.Equal(q0, q1, cmp.AllowUnexported(Query{})) {
t.Errorf("q0 and q1 were not equal")
}
if reflect.DeepEqual(q1, q2) {
if testutil.Equal(q1, q2, cmp.AllowUnexported(Query{})) {
t.Errorf("q1 and q2 were equal")
}
@@ -381,10 +383,10 @@ func TestQueriesAreImmutable(t *testing.T) {
q4 := f()
q5 := q4.Order("y")
q6 := q4.Order("z")
if !reflect.DeepEqual(q3, q5) {
if !testutil.Equal(q3, q5, cmp.AllowUnexported(Query{})) {
t.Errorf("q3 and q5 were not equal")
}
if reflect.DeepEqual(q5, q6) {
if testutil.Equal(q5, q6, cmp.AllowUnexported(Query{})) {
t.Errorf("q5 and q6 were equal")
}
}
@@ -472,6 +474,7 @@ func TestNamespaceQuery(t *testing.T) {
var gs []Gopher
// Ignore errors for the rest of this test.
client.GetAll(ctx, NewQuery("gopher"), &gs)
if got, want := <-gotNamespace, ""; got != want {
t.Errorf("GetAll: got namespace %q, want %q", got, want)
@@ -482,13 +485,11 @@ func TestNamespaceQuery(t *testing.T) {
}
const ns = "not_default"
ctx = WithNamespace(ctx, ns)
client.GetAll(ctx, NewQuery("gopher"), &gs)
client.GetAll(ctx, NewQuery("gopher").Namespace(ns), &gs)
if got, want := <-gotNamespace, ns; got != want {
t.Errorf("GetAll: got namespace %q, want %q", got, want)
}
client.Count(ctx, NewQuery("gopher"))
client.Count(ctx, NewQuery("gopher").Namespace(ns))
if got, want := <-gotNamespace, ns; got != want {
t.Errorf("Count: got namespace %q, want %q", got, want)
}
@@ -509,12 +510,20 @@ func TestReadOptions(t *testing.T) {
want: nil,
},
{
q: NewQuery("").Transaction(&Transaction{id: tid}),
want: &pb.ReadOptions{&pb.ReadOptions_Transaction{tid}},
q: NewQuery("").Transaction(&Transaction{id: tid}),
want: &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{
Transaction: tid,
},
},
},
{
q: NewQuery("").EventualConsistency(),
want: &pb.ReadOptions{&pb.ReadOptions_ReadConsistency_{pb.ReadOptions_EVENTUAL}},
q: NewQuery("").EventualConsistency(),
want: &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_ReadConsistency_{
ReadConsistency: pb.ReadOptions_EVENTUAL,
},
},
},
} {
req := &pb.RunQueryRequest{}
@@ -536,3 +545,26 @@ func TestReadOptions(t *testing.T) {
}
}
}
func TestInvalidFilters(t *testing.T) {
client := &Client{
client: &fakeClient{
queryFn: func(req *pb.RunQueryRequest) (*pb.RunQueryResponse, error) {
return fakeRunQuery(req)
},
},
}
// Used for an invalid type
type MyType int
var v MyType = 1
for _, q := range []*Query{
NewQuery("SomeKey").Filter("", 0),
NewQuery("SomeKey").Filter("fld=", v),
} {
if _, err := client.Count(context.Background(), q); err == nil {
t.Errorf("%+v: got nil, wanted error", q)
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,12 +19,19 @@ import (
"fmt"
"reflect"
"time"
"unicode/utf8"
timepb "github.com/golang/protobuf/ptypes/timestamp"
pb "google.golang.org/genproto/googleapis/datastore/v1"
llpb "google.golang.org/genproto/googleapis/type/latlng"
)
type saveOpts struct {
noIndex bool
flatten bool
omitEmpty bool
}
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
func saveEntity(key *Key, src interface{}) (*pb.Entity, error) {
var err error
@@ -41,10 +48,24 @@ func saveEntity(key *Key, src interface{}) (*pb.Entity, error) {
}
// TODO(djd): Convert this and below to return ([]Property, error).
func saveStructProperty(props *[]Property, name string, noIndex bool, v reflect.Value) error {
func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error {
p := Property{
Name: name,
NoIndex: noIndex,
NoIndex: opts.noIndex,
}
if opts.omitEmpty && isEmptyValue(v) {
return nil
}
// First check if field type implements PLS. If so, use PLS to
// save.
ok, err := plsFieldSave(props, p, name, opts, v)
if err != nil {
return err
}
if ok {
return nil
}
switch x := v.Interface().(type) {
@@ -64,17 +85,60 @@ func saveStructProperty(props *[]Property, name string, noIndex bool, v reflect.
if v.Type().Elem().Kind() == reflect.Uint8 {
p.Value = v.Bytes()
} else {
return saveSliceProperty(props, name, noIndex, v)
return saveSliceProperty(props, name, opts, v)
}
case reflect.Ptr:
if isValidPointerType(v.Type().Elem()) {
if v.IsNil() {
// Nil pointer becomes a nil property value (unless omitempty, handled above).
p.Value = nil
*props = append(*props, p)
return nil
}
// When we recurse on the derefenced pointer, omitempty no longer applies:
// we already know the pointer is not empty, it doesn't matter if its referent
// is empty or not.
opts.omitEmpty = false
return saveStructProperty(props, name, opts, v.Elem())
}
if v.Type().Elem().Kind() != reflect.Struct {
return fmt.Errorf("datastore: unsupported struct field type: %s", v.Type())
}
// Pointer to struct is a special case.
if v.IsNil() {
return nil
}
v = v.Elem()
fallthrough
case reflect.Struct:
if !v.CanAddr() {
return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
}
sub, err := newStructPLS(v.Addr().Interface())
vi := v.Addr().Interface()
sub, err := newStructPLS(vi)
if err != nil {
return fmt.Errorf("datastore: unsupported struct field: %v", err)
}
return sub.save(props, name+".", noIndex)
if opts.flatten {
return sub.save(props, opts, name+".")
}
var subProps []Property
err = sub.save(&subProps, opts, "")
if err != nil {
return err
}
subKey, err := sub.key(v)
if err != nil {
return err
}
p.Value = &Entity{
Key: subKey,
Properties: subProps,
}
}
}
if p.Value == nil {
@@ -84,7 +148,66 @@ func saveStructProperty(props *[]Property, name string, noIndex bool, v reflect.
return nil
}
func saveSliceProperty(props *[]Property, name string, noIndex bool, v reflect.Value) error {
// plsFieldSave first tries to converts v's value to a PLS, then v's addressed
// value to a PLS. If neither succeeds, plsFieldSave returns false for first return
// value.
// If v is successfully converted to a PLS, plsFieldSave will then add the
// Value to property p by way of the PLS's Save method, and append it to props.
//
// If the flatten option is present in opts, name must be prepended to each property's
// name before it is appended to props. Eg. if name were "A" and a subproperty's name
// were "B", the resultant name of the property to be appended to props would be "A.B".
func plsFieldSave(props *[]Property, p Property, name string, opts saveOpts, v reflect.Value) (ok bool, err error) {
vpls, err := plsForSave(v)
if err != nil {
return false, err
}
if vpls == nil {
return false, nil
}
subProps, err := vpls.Save()
if err != nil {
return true, err
}
if opts.flatten {
for _, subp := range subProps {
subp.Name = name + "." + subp.Name
*props = append(*props, subp)
}
return true, nil
}
p.Value = &Entity{Properties: subProps}
*props = append(*props, p)
return true, nil
}
// key extracts the *Key struct field from struct v based on the structCodec of s.
func (s structPLS) key(v reflect.Value) (*Key, error) {
if v.Kind() != reflect.Struct {
return nil, errors.New("datastore: cannot save key of non-struct type")
}
keyField := s.codec.Match(keyFieldName)
if keyField == nil {
return nil, nil
}
f := v.FieldByIndex(keyField.Index)
k, ok := f.Interface().(*Key)
if !ok {
return nil, fmt.Errorf("datastore: %s field on struct %T is not a *datastore.Key", keyFieldName, v.Interface())
}
return k, nil
}
func saveSliceProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error {
// Easy case: if the slice is empty, we're done.
if v.Len() == 0 {
return nil
@@ -92,7 +215,7 @@ func saveSliceProperty(props *[]Property, name string, noIndex bool, v reflect.V
// Work out the properties generated by the first element in the slice. This will
// usually be a single property, but will be more if this is a slice of structs.
var headProps []Property
if err := saveStructProperty(&headProps, name, noIndex, v.Index(0)); err != nil {
if err := saveStructProperty(&headProps, name, opts, v.Index(0)); err != nil {
return err
}
@@ -106,7 +229,7 @@ func saveSliceProperty(props *[]Property, name string, noIndex bool, v reflect.V
// Find the elements for the subsequent elements.
for i := 1; i < v.Len(); i++ {
elemProps := make([]Property, 0, len(headProps))
if err := saveStructProperty(&elemProps, name, noIndex, v.Index(i)); err != nil {
if err := saveStructProperty(&elemProps, name, opts, v.Index(i)); err != nil {
return err
}
for _, p := range elemProps {
@@ -128,27 +251,58 @@ func saveSliceProperty(props *[]Property, name string, noIndex bool, v reflect.V
func (s structPLS) Save() ([]Property, error) {
var props []Property
if err := s.save(&props, "", false); err != nil {
if err := s.save(&props, saveOpts{}, ""); err != nil {
return nil, err
}
return props, nil
}
func (s structPLS) save(props *[]Property, prefix string, noIndex bool) error {
for name, f := range s.codec.fields {
name = prefix + name
v := s.v.FieldByIndex(f.path)
func (s structPLS) save(props *[]Property, opts saveOpts, prefix string) error {
for _, f := range s.codec {
name := prefix + f.Name
v := getField(s.v, f.Index)
if !v.IsValid() || !v.CanSet() {
continue
}
noIndex1 := noIndex || f.noIndex
if err := saveStructProperty(props, name, noIndex1, v); err != nil {
var tagOpts saveOpts
if f.ParsedTag != nil {
tagOpts = f.ParsedTag.(saveOpts)
}
var opts1 saveOpts
opts1.noIndex = opts.noIndex || tagOpts.noIndex
opts1.flatten = opts.flatten || tagOpts.flatten
opts1.omitEmpty = tagOpts.omitEmpty // don't propagate
if err := saveStructProperty(props, name, opts1, v); err != nil {
return err
}
}
return nil
}
// getField returns the field from v at the given index path.
// If it encounters a nil-valued field in the path, getField
// stops and returns a zero-valued reflect.Value, preventing the
// panic that would have been caused by reflect's FieldByIndex.
func getField(v reflect.Value, index []int) reflect.Value {
var zero reflect.Value
if v.Type().Kind() != reflect.Struct {
return zero
}
for _, i := range index {
if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
if v.IsNil() {
return zero
}
v = v.Elem()
}
v = v.Field(i)
}
return v
}
func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) {
e := &pb.Entity{
Key: keyToProto(key),
@@ -156,6 +310,11 @@ func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) {
}
indexedProps := 0
for _, p := range props {
// Do not send a Key value a field to datastore.
if p.Name == keyFieldName {
continue
}
val, err := interfaceToProto(p.Value, p.NoIndex)
if err != nil {
return nil, fmt.Errorf("datastore: %v for a Property with Name %q", err, p.Name)
@@ -184,33 +343,36 @@ func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
val := &pb.Value{ExcludeFromIndexes: noIndex}
switch v := iv.(type) {
case int:
val.ValueType = &pb.Value_IntegerValue{int64(v)}
val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)}
case int32:
val.ValueType = &pb.Value_IntegerValue{int64(v)}
val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)}
case int64:
val.ValueType = &pb.Value_IntegerValue{v}
val.ValueType = &pb.Value_IntegerValue{IntegerValue: v}
case bool:
val.ValueType = &pb.Value_BooleanValue{v}
val.ValueType = &pb.Value_BooleanValue{BooleanValue: v}
case string:
if len(v) > 1500 && !noIndex {
return nil, errors.New("string property too long to index")
}
val.ValueType = &pb.Value_StringValue{v}
if !utf8.ValidString(v) {
return nil, fmt.Errorf("string is not valid utf8: %q", v)
}
val.ValueType = &pb.Value_StringValue{StringValue: v}
case float32:
val.ValueType = &pb.Value_DoubleValue{float64(v)}
val.ValueType = &pb.Value_DoubleValue{DoubleValue: float64(v)}
case float64:
val.ValueType = &pb.Value_DoubleValue{v}
val.ValueType = &pb.Value_DoubleValue{DoubleValue: v}
case *Key:
if v == nil {
val.ValueType = &pb.Value_NullValue{}
} else {
val.ValueType = &pb.Value_KeyValue{keyToProto(v)}
val.ValueType = &pb.Value_KeyValue{KeyValue: keyToProto(v)}
}
case GeoPoint:
if !v.Valid() {
return nil, errors.New("invalid GeoPoint value")
}
val.ValueType = &pb.Value_GeoPointValue{&llpb.LatLng{
val.ValueType = &pb.Value_GeoPointValue{GeoPointValue: &llpb.LatLng{
Latitude: v.Lat,
Longitude: v.Lng,
}}
@@ -218,7 +380,7 @@ func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
if v.Before(minTime) || v.After(maxTime) {
return nil, errors.New("time value out of range")
}
val.ValueType = &pb.Value_TimestampValue{&timepb.Timestamp{
val.ValueType = &pb.Value_TimestampValue{TimestampValue: &timepb.Timestamp{
Seconds: v.Unix(),
Nanos: int32(v.Nanosecond()),
}}
@@ -226,7 +388,13 @@ func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
if len(v) > 1500 && !noIndex {
return nil, errors.New("[]byte property too long to index")
}
val.ValueType = &pb.Value_BlobValue{v}
val.ValueType = &pb.Value_BlobValue{BlobValue: v}
case *Entity:
e, err := propertiesToProto(v.Key, v.Properties)
if err != nil {
return nil, err
}
val.ValueType = &pb.Value_EntityValue{EntityValue: e}
case []interface{}:
arr := make([]*pb.Value, 0, len(v))
for i, v := range v {
@@ -236,16 +404,67 @@ func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
}
arr = append(arr, elem)
}
val.ValueType = &pb.Value_ArrayValue{&pb.ArrayValue{arr}}
val.ValueType = &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: arr}}
// ArrayValues have ExcludeFromIndexes set on the individual items, rather
// than the top-level value.
val.ExcludeFromIndexes = false
default:
if iv != nil {
return nil, fmt.Errorf("invalid Value type %t", iv)
rv := reflect.ValueOf(iv)
if !rv.IsValid() {
val.ValueType = &pb.Value_NullValue{}
} else if rv.Kind() == reflect.Ptr { // non-nil pointer: dereference
if rv.IsNil() {
val.ValueType = &pb.Value_NullValue{}
return val, nil
}
return interfaceToProto(rv.Elem().Interface(), noIndex)
} else {
return nil, fmt.Errorf("invalid Value type %T", iv)
}
val.ValueType = &pb.Value_NullValue{}
}
// TODO(jbd): Support EntityValue.
return val, nil
}
// isEmptyValue is taken from the encoding/json package in the
// standard library.
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()
case reflect.Struct:
if t, ok := v.Interface().(time.Time); ok {
return t.IsZero()
}
}
return false
}
// isValidPointerType reports whether a struct field can be a pointer to type t
// for the purposes of saving and loading.
func isValidPointerType(t reflect.Type) bool {
if t == typeOfTime || t == typeOfGeoPoint {
return true
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
case reflect.Bool:
return true
case reflect.String:
return true
case reflect.Float32, reflect.Float64:
return true
}
return false
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All Rights Reserved.
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,19 +16,274 @@ package datastore
import (
"testing"
"time"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestInterfaceToProtoNilKey(t *testing.T) {
var iv *Key
pv, err := interfaceToProto(iv, false)
if err != nil {
t.Fatalf("nil key: interfaceToProto: %v", err)
}
_, ok := pv.ValueType.(*pb.Value_NullValue)
if !ok {
t.Errorf("nil key: type:\ngot: %T\nwant: %T", pv.ValueType, &pb.Value_NullValue{})
func TestInterfaceToProtoNil(t *testing.T) {
// A nil *Key, or a nil value of any other pointer type, should convert to a NullValue.
for _, in := range []interface{}{
(*Key)(nil),
(*int)(nil),
(*string)(nil),
(*bool)(nil),
(*float64)(nil),
(*GeoPoint)(nil),
(*time.Time)(nil),
} {
got, err := interfaceToProto(in, false)
if err != nil {
t.Fatalf("%T: %v", in, err)
}
_, ok := got.ValueType.(*pb.Value_NullValue)
if !ok {
t.Errorf("%T: got: %T\nwant: %T", in, got.ValueType, &pb.Value_NullValue{})
}
}
}
func TestSaveEntityNested(t *testing.T) {
type WithKey struct {
X string
I int
K *Key `datastore:"__key__"`
}
type NestedWithKey struct {
Y string
N WithKey
}
type WithoutKey struct {
X string
I int
}
type NestedWithoutKey struct {
Y string
N WithoutKey
}
type a struct {
S string
}
type UnexpAnonym struct {
a
}
testCases := []struct {
desc string
src interface{}
key *Key
want *pb.Entity
}{
{
desc: "nested entity with key",
src: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
I: 2,
K: testKey1a,
},
},
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
"N": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Key: keyToProto(testKey1a),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
},
{
desc: "nested entity with incomplete key",
src: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
I: 2,
K: incompleteKey,
},
},
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
"N": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Key: keyToProto(incompleteKey),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
},
{
desc: "nested entity without key",
src: &NestedWithoutKey{
Y: "yyy",
N: WithoutKey{
X: "two",
I: 2,
},
},
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
"N": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
},
{
desc: "key at top level",
src: &WithKey{
X: "three",
I: 3,
K: testKey0,
},
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{StringValue: "three"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
},
},
},
{
desc: "nested unexported anonymous struct field",
src: &UnexpAnonym{
a{S: "hello"},
},
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"S": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
},
},
},
}
for _, tc := range testCases {
got, err := saveEntity(tc.key, tc.src)
if err != nil {
t.Errorf("saveEntity: %s: %v", tc.desc, err)
continue
}
if !testutil.Equal(tc.want, got) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, got, tc.want)
}
}
}
func TestSavePointers(t *testing.T) {
for _, test := range []struct {
desc string
in interface{}
want []Property
}{
{
desc: "nil pointers save as nil-valued properties",
in: &Pointers{},
want: []Property{
{Name: "Pi", Value: nil},
{Name: "Ps", Value: nil},
{Name: "Pb", Value: nil},
{Name: "Pf", Value: nil},
{Name: "Pg", Value: nil},
{Name: "Pt", Value: nil},
},
},
{
desc: "nil omitempty pointers not saved",
in: &PointersOmitEmpty{},
want: []Property(nil),
},
{
desc: "non-nil omitempty zero-valued pointers are saved",
in: func() *PointersOmitEmpty { pi := 0; return &PointersOmitEmpty{Pi: &pi} }(),
want: []Property{{Name: "Pi", Value: int64(0)}},
},
{
desc: "non-nil zero-valued pointers save as zero values",
in: populatedPointers(),
want: []Property{
{Name: "Pi", Value: int64(0)},
{Name: "Ps", Value: ""},
{Name: "Pb", Value: false},
{Name: "Pf", Value: 0.0},
{Name: "Pg", Value: GeoPoint{}},
{Name: "Pt", Value: time.Time{}},
},
},
{
desc: "non-nil non-zero-valued pointers save as the appropriate values",
in: func() *Pointers {
p := populatedPointers()
*p.Pi = 1
*p.Ps = "x"
*p.Pb = true
*p.Pf = 3.14
*p.Pg = GeoPoint{Lat: 1, Lng: 2}
*p.Pt = time.Unix(100, 0)
return p
}(),
want: []Property{
{Name: "Pi", Value: int64(1)},
{Name: "Ps", Value: "x"},
{Name: "Pb", Value: true},
{Name: "Pf", Value: 3.14},
{Name: "Pg", Value: GeoPoint{Lat: 1, Lng: 2}},
{Name: "Pt", Value: time.Unix(100, 0)},
},
},
} {
got, err := SaveStruct(test.in)
if err != nil {
t.Fatalf("%s: %v", test.desc, err)
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s\ngot %#v\nwant %#v\n", test.desc, got, test.want)
}
}
}
func TestSaveEmptySlice(t *testing.T) {
// Zero-length slice fields are not saved.
for _, slice := range [][]string{nil, {}} {
got, err := SaveStruct(&struct{ S []string }{S: slice})
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("%#v: got %d properties, wanted zero", slice, len(got))
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,13 +15,13 @@
package datastore
import (
"context"
"errors"
"golang.org/x/net/context"
"cloud.google.com/go/internal/trace"
pb "google.golang.org/genproto/googleapis/datastore/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
// ErrConcurrentTransaction is returned when a transaction is rolled back due
@@ -32,6 +32,8 @@ var errExpiredTransaction = errors.New("datastore: transaction expired")
type transactionSettings struct {
attempts int
readOnly bool
prevID []byte // ID of the transaction to retry
}
// newTransactionSettings creates a transactionSettings with a given TransactionOption slice.
@@ -62,6 +64,19 @@ func (w maxAttempts) apply(s *transactionSettings) {
}
}
// ReadOnly is a TransactionOption that marks the transaction as read-only.
var ReadOnly TransactionOption
func init() {
ReadOnly = readOnly{}
}
type readOnly struct{}
func (readOnly) apply(s *transactionSettings) {
s.readOnly = true
}
// Transaction represents a set of datastore operations to be committed atomically.
//
// Operations are enqueued by calling the Put and Delete methods on Transaction
@@ -80,20 +95,35 @@ type Transaction struct {
}
// NewTransaction starts a new transaction.
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (*Transaction, error) {
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (t *Transaction, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.NewTransaction")
defer func() { trace.EndSpan(ctx, err) }()
for _, o := range opts {
if _, ok := o.(maxAttempts); ok {
return nil, errors.New("datastore: NewTransaction does not accept MaxAttempts option")
}
}
req := &pb.BeginTransactionRequest{
ProjectId: c.dataset,
return c.newTransaction(ctx, newTransactionSettings(opts))
}
func (c *Client) newTransaction(ctx context.Context, s *transactionSettings) (*Transaction, error) {
req := &pb.BeginTransactionRequest{ProjectId: c.dataset}
if s.readOnly {
req.TransactionOptions = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
}
} else if s.prevID != nil {
req.TransactionOptions = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
PreviousTransaction: s.prevID,
}},
}
}
resp, err := c.client.BeginTransaction(ctx, req)
if err != nil {
return nil, err
}
return &Transaction{
id: resp.Transaction,
ctx: ctx,
@@ -121,48 +151,60 @@ func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption)
// must not assume that any of f's changes have been committed until
// RunInTransaction returns nil.
//
// Since f may be called multiple times, f should usually be idempotent.
// Note that Transaction.Get is not idempotent when unmarshaling slice fields.
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (*Commit, error) {
// Since f may be called multiple times, f should usually be idempotent that
// is, it should have the same result when called multiple times. Note that
// Transaction.Get will append when unmarshalling slice fields, so it is not
// necessarily idempotent.
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (cmt *Commit, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.RunInTransaction")
defer func() { trace.EndSpan(ctx, err) }()
settings := newTransactionSettings(opts)
for n := 0; n < settings.attempts; n++ {
tx, err := c.NewTransaction(ctx)
tx, err := c.newTransaction(ctx, settings)
if err != nil {
return nil, err
}
if err := f(tx); err != nil {
tx.Rollback()
_ = tx.Rollback()
return nil, err
}
if cmt, err := tx.Commit(); err != ErrConcurrentTransaction {
return cmt, err
}
// Pass this transaction's ID to the retry transaction to preserve
// transaction priority.
if !settings.readOnly {
settings.prevID = tx.id
}
}
return nil, ErrConcurrentTransaction
}
// Commit applies the enqueued operations atomically.
func (t *Transaction) Commit() (*Commit, error) {
func (t *Transaction) Commit() (c *Commit, err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Commit")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return nil, errExpiredTransaction
}
req := &pb.CommitRequest{
ProjectId: t.client.dataset,
TransactionSelector: &pb.CommitRequest_Transaction{t.id},
TransactionSelector: &pb.CommitRequest_Transaction{Transaction: t.id},
Mutations: t.mutations,
Mode: pb.CommitRequest_TRANSACTIONAL,
}
t.id = nil
resp, err := t.client.client.Commit(t.ctx, req)
if grpc.Code(err) == codes.Aborted {
return nil, ErrConcurrentTransaction
}
t.id = nil // mark the transaction as expired
if err != nil {
if grpc.Code(err) == codes.Aborted {
return nil, ErrConcurrentTransaction
}
return nil, err
}
// Copy any newly minted keys into the returned keys.
commit := &Commit{}
for i, p := range t.pending {
if i >= len(resp.MutationResults) || resp.MutationResults[i].Key == nil {
return nil, errors.New("datastore: internal error: server returned the wrong mutation results")
@@ -172,20 +214,23 @@ func (t *Transaction) Commit() (*Commit, error) {
return nil, errors.New("datastore: internal error: server returned an invalid key")
}
p.key = key
p.commit = commit
p.commit = c
}
return commit, nil
return c, nil
}
// Rollback abandons a pending transaction.
func (t *Transaction) Rollback() error {
func (t *Transaction) Rollback() (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Rollback")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return errExpiredTransaction
}
id := t.id
t.id = nil
_, err := t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
_, err = t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
ProjectId: t.client.dataset,
Transaction: id,
})
@@ -197,11 +242,14 @@ func (t *Transaction) Rollback() error {
// snapshot. Furthermore, if the transaction is set to a serializable isolation
// level, another transaction cannot concurrently modify the data that is read
// or modified by this transaction.
func (t *Transaction) Get(key *Key, dst interface{}) error {
func (t *Transaction) Get(key *Key, dst interface{}) (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Get")
defer func() { trace.EndSpan(t.ctx, err) }()
opts := &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{t.id},
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
}
err := t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
err = t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
if me, ok := err.(MultiError); ok {
return me[0]
}
@@ -209,12 +257,15 @@ func (t *Transaction) Get(key *Key, dst interface{}) error {
}
// GetMulti is a batch version of Get.
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) error {
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.GetMulti")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return errExpiredTransaction
}
opts := &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{t.id},
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
}
return t.client.get(t.ctx, keys, dst, opts)
}
@@ -238,7 +289,8 @@ func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) {
// PutMulti is a batch version of Put. One PendingKey is returned for each
// element of src in the same order.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) ([]*PendingKey, error) {
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) (ret []*PendingKey, err error) {
if t.id == nil {
return nil, errExpiredTransaction
}
@@ -250,7 +302,7 @@ func (t *Transaction) PutMulti(keys []*Key, src interface{}) ([]*PendingKey, err
t.mutations = append(t.mutations, mutations...)
// Prepare the returned handles, pre-populating where possible.
ret := make([]*PendingKey, len(keys))
ret = make([]*PendingKey, len(keys))
for i, key := range keys {
p := &PendingKey{}
if key.Incomplete() {
@@ -277,7 +329,8 @@ func (t *Transaction) Delete(key *Key) error {
}
// DeleteMulti is a batch version of Delete.
func (t *Transaction) DeleteMulti(keys []*Key) error {
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) DeleteMulti(keys []*Key) (err error) {
if t.id == nil {
return errExpiredTransaction
}
@@ -289,12 +342,53 @@ func (t *Transaction) DeleteMulti(keys []*Key) error {
return nil
}
// Mutate adds the mutations to the transaction. They will all be applied atomically
// upon calling Commit. Mutate returns a PendingKey for each Mutation in the argument
// list, in the same order. PendingKeys for Delete mutations are always nil.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
//
// For an example, see Client.Mutate.
func (t *Transaction) Mutate(muts ...*Mutation) ([]*PendingKey, error) {
if t.id == nil {
return nil, errExpiredTransaction
}
pmuts, err := mutationProtos(muts)
if err != nil {
return nil, err
}
origin := len(t.mutations)
t.mutations = append(t.mutations, pmuts...)
// Prepare the returned handles, pre-populating where possible.
ret := make([]*PendingKey, len(muts))
for i, mut := range muts {
if mut.isDelete() {
continue
}
p := &PendingKey{}
if mut.key.Incomplete() {
// This key will be in the final commit result.
t.pending[origin+i] = p
} else {
p.key = mut.key
}
ret[i] = p
}
return ret, nil
}
// Commit represents the result of a committed transaction.
type Commit struct{}
// Key resolves a pending key handle into a final key.
func (c *Commit) Key(p *PendingKey) *Key {
if c != p.commit {
if p == nil { // if called on a *PendingKey from a Delete mutation
return nil
}
// If p.commit is nil, the PendingKey did not come from an incomplete key,
// so p.key is valid.
if p.commit != nil && c != p.commit {
panic("PendingKey was not created by corresponding transaction")
}
return p.key

View File

@@ -0,0 +1,77 @@
// Copyright 2017 Google LLC
//
// 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 datastore
import (
"context"
"testing"
"github.com/golang/protobuf/proto"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestNewTransaction(t *testing.T) {
var got *pb.BeginTransactionRequest
client := &Client{
dataset: "project",
client: &fakeDatastoreClient{
beginTransaction: func(req *pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) {
got = req
return &pb.BeginTransactionResponse{
Transaction: []byte("tid"),
}, nil
},
},
}
ctx := context.Background()
for _, test := range []struct {
settings *transactionSettings
want *pb.BeginTransactionRequest
}{
{
&transactionSettings{},
&pb.BeginTransactionRequest{ProjectId: "project"},
},
{
&transactionSettings{readOnly: true},
&pb.BeginTransactionRequest{
ProjectId: "project",
TransactionOptions: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
},
},
},
{
&transactionSettings{prevID: []byte("tid")},
&pb.BeginTransactionRequest{
ProjectId: "project",
TransactionOptions: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
PreviousTransaction: []byte("tid"),
},
},
},
},
},
} {
_, err := client.newTransaction(ctx, test.settings)
if err != nil {
t.Fatal(err)
}
if !proto.Equal(got, test.want) {
t.Errorf("%+v:\ngot %+v\nwant %+v", test.settings, got, test.want)
}
}
}