vendor: update all dependencies

This commit is contained in:
Nick Craig-Wood
2017-07-23 08:51:42 +01:00
parent 0b6fba34a3
commit eb87cf6f12
2008 changed files with 352617 additions and 1004734 deletions
+15 -11
View File
@@ -53,6 +53,10 @@ type protoClient interface {
// 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
}
@@ -67,27 +71,27 @@ func newDatastoreClient(conn *grpc.ClientConn, projectID string) pb.DatastoreCli
}
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...)
return dc.c.Lookup(metadata.NewOutgoingContext(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...)
return dc.c.RunQuery(metadata.NewOutgoingContext(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...)
return dc.c.BeginTransaction(metadata.NewOutgoingContext(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...)
return dc.c.Commit(metadata.NewOutgoingContext(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...)
return dc.c.Rollback(metadata.NewOutgoingContext(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...)
return dc.c.AllocateIds(metadata.NewOutgoingContext(ctx, dc.md), in, opts...)
}
// Client is a client for reading and writing data in a datastore dataset.
@@ -202,9 +206,9 @@ func keyToProto(k *Key) *pb.Key {
for {
el := &pb.Key_PathElement{Kind: k.Kind}
if k.ID != 0 {
el.IdType = &pb.Key_PathElement_Id{k.ID}
el.IdType = &pb.Key_PathElement_Id{Id: k.ID}
} else if k.Name != "" {
el.IdType = &pb.Key_PathElement_Name{k.Name}
el.IdType = &pb.Key_PathElement_Name{Name: k.Name}
}
path = append([]*pb.Key_PathElement{el}, path...)
if k.Parent == nil {
@@ -549,9 +553,9 @@ func putMutations(keys []*Key, src interface{}) ([]*pb.Mutation, error) {
}
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)
}
@@ -593,7 +597,7 @@ func deleteMutations(keys []*Key) ([]*pb.Mutation, error) {
return nil, fmt.Errorf("datastore: can't delete the incomplete key: %v", k)
}
mutations = append(mutations, &pb.Mutation{
Operation: &pb.Mutation_Delete{keyToProto(k)},
Operation: &pb.Mutation_Delete{Delete: keyToProto(k)},
})
}
return mutations, nil
+766 -79
View File
@@ -1954,7 +1954,7 @@ func TestRoundTrip(t *testing.T) {
equal = reflect.DeepEqual(got, tc.want)
}
if !equal {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, got, tc.want)
t.Errorf("%s: compare:\ngot: %+#v\nwant: %+#v", tc.desc, got, tc.want)
continue
}
}
@@ -1986,6 +1986,19 @@ func (pls aValuePLS) Save() ([]Property, error) {
return []Property{{Name: "Count", Value: 8}}, nil
}
type aValuePtrPLS struct {
Count int
}
func (pls *aValuePtrPLS) Load([]Property) error {
pls.Count = 11
return nil
}
func (pls *aValuePtrPLS) Save() ([]Property, error) {
return []Property{{Name: "Count", Value: 12}}, nil
}
type aNotPLS struct {
Count int
}
@@ -2001,15 +2014,21 @@ func (s *plsString) Save() ([]Property, error) {
return []Property{{Name: "SS", Value: "SAVED"}}, nil
}
func ptrToplsString(s string) *plsString {
plsStr := plsString(s)
return &plsStr
}
type aSubPLS struct {
Foo string
Bar *aPtrPLS
Baz aValuePtrPLS
S plsString
}
type aSubNotPLS struct {
Foo string
Bar *aNotPLS
S plsString `datastore:",omitempty"`
}
type aSubPLSErr struct {
@@ -2017,77 +2036,203 @@ type aSubPLSErr struct {
Bar aValuePLS
}
func TestLoadSaveNestedStructPLS(t *testing.T) {
type aSubPLSNoErr struct {
Foo string
Bar aPtrPLS
}
type GrandparentFlatten struct {
Parent Parent `datastore:",flatten"`
}
type GrandparentOfPtrFlatten struct {
Parent ParentOfPtr `datastore:",flatten"`
}
type GrandparentOfSlice struct {
Parent ParentOfSlice
}
type GrandparentOfSlicePtrs struct {
Parent ParentOfSlicePtrs
}
type GrandparentOfSliceFlatten struct {
Parent ParentOfSlice `datastore:",flatten"`
}
type GrandparentOfSlicePtrsFlatten struct {
Parent ParentOfSlicePtrs `datastore:",flatten"`
}
type Grandparent struct {
Parent Parent
}
type Parent struct {
Child Child
String plsString
}
type ParentOfPtr struct {
Child *Child
String *plsString
}
type ParentOfSlice struct {
Children []Child
Strings []plsString
}
type ParentOfSlicePtrs struct {
Children []*Child
Strings []*plsString
}
type Child struct {
I int
Grandchild Grandchild
}
type Grandchild struct {
S string
}
func (c *Child) Load(props []Property) error {
for _, p := range props {
if p.Name == "I" {
c.I += 1
} else if p.Name == "Grandchild.S" {
c.Grandchild.S = "grandchild loaded"
}
}
return nil
}
func (c *Child) Save() ([]Property, error) {
v := c.I + 1
return []Property{
{Name: "I", Value: v},
{Name: "Grandchild.S", Value: fmt.Sprintf("grandchild saved %d", v)},
}, nil
}
func TestLoadSavePLS(t *testing.T) {
type testCase struct {
desc string
src interface{}
wantSave *pb.Entity
wantLoad interface{}
saveErr string
loadErr string
}
testCases := []testCase{
{
desc: "substruct (ptr) does implement PLS",
src: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 2}},
desc: "non-struct implements PLS (top-level)",
src: ptrToplsString("hello"),
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{"foo"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
wantLoad: ptrToplsString("LOADED"),
},
{
desc: "substructs do implement PLS",
src: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 2}, Baz: aValuePtrPLS{Count: 15}, S: "something"},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
"Bar": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Count": {ValueType: &pb.Value_IntegerValue{4}},
"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}},
},
},
}},
"Baz": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}},
},
},
}},
"S": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
},
},
// PLS impl for 'S' not used, not entity.
wantLoad: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 1}},
wantLoad: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 1}, Baz: aValuePtrPLS{Count: 11}, S: "LOADED"},
},
{
desc: "substruct (ptr) does implement PLS, nil valued substruct",
src: &aSubPLS{Foo: "foo"},
src: &aSubPLS{Foo: "foo", S: "something"},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{"foo"}},
},
},
wantLoad: &aSubPLS{Foo: "foo"},
},
{
desc: "substruct (ptr) does not implement PLS",
src: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}, S: "something"},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{"foo"}},
"Bar": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
"Baz": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Count": {ValueType: &pb.Value_IntegerValue{2}},
"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}},
},
},
}},
"S": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
// PLS impl for 'S' not used, not entity.
"S": {ValueType: &pb.Value_StringValue{"something"}},
},
},
wantLoad: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}, S: "something"},
wantLoad: &aSubPLS{Foo: "foo", Baz: aValuePtrPLS{Count: 11}, S: "LOADED"},
},
{
desc: "substruct (value) does implement PLS, error",
src: &aSubPLSErr{Foo: "foo", Bar: aValuePLS{Count: 3}},
desc: "substruct (ptr) does not implement PLS",
src: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{"foo"}},
"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
"Bar": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Count": {ValueType: &pb.Value_IntegerValue{8}},
"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
},
},
wantLoad: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}},
},
{
desc: "substruct (value) does implement PLS, error on save",
src: &aSubPLSErr{Foo: "foo", Bar: aValuePLS{Count: 2}},
wantSave: (*pb.Entity)(nil),
wantLoad: &aSubPLSErr{},
saveErr: "PropertyLoadSaver methods must be implemented on a pointer",
},
{
desc: "substruct (value) does implement PLS, error on load",
src: &aSubPLSNoErr{Foo: "foo", Bar: aPtrPLS{Count: 2}},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
"Bar": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}},
},
},
}},
@@ -2096,45 +2241,508 @@ func TestLoadSaveNestedStructPLS(t *testing.T) {
wantLoad: &aSubPLSErr{},
loadErr: "PropertyLoadSaver methods must be implemented on a pointer",
},
{
desc: "parent does not have flatten option, child impl PLS",
src: &Grandparent{
Parent: Parent{
Child: Child{
I: 9,
Grandchild: Grandchild{
S: "BAD",
},
},
String: plsString("something"),
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Child": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
},
},
}},
"String": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
},
},
}},
},
},
wantLoad: &Grandparent{
Parent: Parent{
Child: Child{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
String: "LOADED",
},
},
},
{
desc: "parent has flatten option enabled, child impl PLS",
src: &GrandparentFlatten{
Parent: Parent{
Child: Child{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
String: plsString("something"),
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
"Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
"Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
wantLoad: &GrandparentFlatten{
Parent: Parent{
Child: Child{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
String: "LOADED",
},
},
},
{
desc: "parent has flatten option enabled, child (ptr to) impl PLS",
src: &GrandparentOfPtrFlatten{
Parent: ParentOfPtr{
Child: &Child{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
String: ptrToplsString("something"),
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
"Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
"Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
wantLoad: &GrandparentOfPtrFlatten{
Parent: ParentOfPtr{
Child: &Child{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
String: ptrToplsString("LOADED"),
},
},
},
{
desc: "children (slice of) impl PLS",
src: &GrandparentOfSlice{
Parent: ParentOfSlice{
Children: []Child{
{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
{
I: 9,
Grandchild: Grandchild{
S: "BAD2",
},
},
},
Strings: []plsString{
"something1",
"something2",
},
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Children": {ValueType: &pb.Value_ArrayValue{
ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
},
},
}},
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
},
},
}},
}},
}},
"Strings": {ValueType: &pb.Value_ArrayValue{
ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
}},
}},
},
},
}},
},
},
wantLoad: &GrandparentOfSlice{
Parent: ParentOfSlice{
Children: []Child{
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
},
Strings: []plsString{
"LOADED",
"LOADED",
},
},
},
},
{
desc: "children (slice of ptrs) impl PLS",
src: &GrandparentOfSlicePtrs{
Parent: ParentOfSlicePtrs{
Children: []*Child{
{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
{
I: 9,
Grandchild: Grandchild{
S: "BAD2",
},
},
},
Strings: []*plsString{
ptrToplsString("something1"),
ptrToplsString("something2"),
},
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent": {ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"Children": {ValueType: &pb.Value_ArrayValue{
ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
},
},
}},
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
},
},
}},
}},
}},
"Strings": {ValueType: &pb.Value_ArrayValue{
ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
{ValueType: &pb.Value_EntityValue{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
}},
}},
},
},
}},
},
},
wantLoad: &GrandparentOfSlicePtrs{
Parent: ParentOfSlicePtrs{
Children: []*Child{
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
},
Strings: []*plsString{
ptrToplsString("LOADED"),
ptrToplsString("LOADED"),
},
},
},
},
{
desc: "parent has flatten option, children (slice of) impl PLS",
src: &GrandparentOfSliceFlatten{
Parent: ParentOfSlice{
Children: []Child{
{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
{
I: 9,
Grandchild: Grandchild{
S: "BAD2",
},
},
},
Strings: []plsString{
"something1",
"something2",
},
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
{ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
},
},
}},
"Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
},
},
}},
"Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
},
},
wantLoad: &GrandparentOfSliceFlatten{
Parent: ParentOfSlice{
Children: []Child{
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
},
Strings: []plsString{
"LOADED",
"LOADED",
},
},
},
},
{
desc: "parent has flatten option, children (slice of ptrs) impl PLS",
src: &GrandparentOfSlicePtrsFlatten{
Parent: ParentOfSlicePtrs{
Children: []*Child{
{
I: 7,
Grandchild: Grandchild{
S: "BAD",
},
},
{
I: 9,
Grandchild: Grandchild{
S: "BAD2",
},
},
},
Strings: []*plsString{
ptrToplsString("something1"),
ptrToplsString("something1"),
},
},
},
wantSave: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
{ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
},
},
}},
"Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
},
},
}},
"Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
Values: []*pb.Value{
{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
},
},
}},
},
},
wantLoad: &GrandparentOfSlicePtrsFlatten{
Parent: ParentOfSlicePtrs{
Children: []*Child{
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
{
I: 1,
Grandchild: Grandchild{
S: "grandchild loaded",
},
},
},
Strings: []*plsString{
ptrToplsString("LOADED"),
ptrToplsString("LOADED"),
},
},
},
},
}
for _, tc := range testCases {
e, err := saveEntity(testKey0, tc.src)
if err != nil {
t.Errorf("%s: save: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(e, tc.wantSave) {
t.Errorf("%s: save: got: %#v, want: %#v", tc.desc, e, tc.wantSave)
if tc.saveErr == "" { // Want no error.
if err != nil {
t.Errorf("%s: save: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(e, tc.wantSave) {
t.Errorf("%s: save: \ngot: %+v\nwant: %+v", tc.desc, e, tc.wantSave)
continue
}
} else { // Want error.
if err == nil {
t.Errorf("%s: save: want err", tc.desc)
continue
}
if !strings.Contains(err.Error(), tc.saveErr) {
t.Errorf("%s: save: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.saveErr)
}
continue
}
gota := reflect.New(reflect.TypeOf(tc.wantLoad).Elem()).Interface()
err = loadEntityProto(gota, e)
switch tc.loadErr {
case "":
if tc.loadErr == "" { // Want no error.
if err != nil {
t.Errorf("%s: load: %v", tc.desc, err)
continue
}
default:
if !reflect.DeepEqual(gota, tc.wantLoad) {
t.Errorf("%s: load: \ngot: %+v\nwant: %+v", tc.desc, gota, tc.wantLoad)
continue
}
} else { // Want error.
if err == nil {
t.Errorf("%s: load: want err", tc.desc)
continue
}
if !strings.Contains(err.Error(), tc.loadErr) {
t.Errorf("%s: load: want err '%s', got '%s'", tc.desc, err.Error(), tc.loadErr)
t.Errorf("%s: load: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.loadErr)
}
continue
}
if !reflect.DeepEqual(tc.wantLoad, gota) {
t.Errorf("%s: load: got: %#v, want: %#v", tc.desc, gota, tc.wantLoad)
continue
}
}
}
func TestQueryConstruction(t *testing.T) {
@@ -2349,20 +2957,22 @@ func TestPutMultiTypes(t *testing.T) {
NameKey("testKind", "second", nil),
}
want := []*pb.Mutation{
{Operation: &pb.Mutation_Upsert{&pb.Entity{
Key: keyToProto(keys[0]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{1}},
"B": {ValueType: &pb.Value_StringValue{"one"}},
},
}}},
{Operation: &pb.Mutation_Upsert{&pb.Entity{
Key: keyToProto(keys[1]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{2}},
"B": {ValueType: &pb.Value_StringValue{"two"}},
},
}}},
{Operation: &pb.Mutation_Upsert{
Upsert: &pb.Entity{
Key: keyToProto(keys[0]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
},
}}},
{Operation: &pb.Mutation_Upsert{
Upsert: &pb.Entity{
Key: keyToProto(keys[1]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
},
}}},
}
for _, tt := range testCases {
@@ -2423,11 +3033,11 @@ func TestNoIndexOnSliceProperties(t *testing.T) {
}
want := &pb.Value{
ValueType: &pb.Value_ArrayValue{&pb.ArrayValue{[]*pb.Value{
{ValueType: &pb.Value_IntegerValue{123}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_BooleanValue{false}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_StringValue{"short"}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_StringValue{strings.Repeat("a", 1503)}, ExcludeFromIndexes: true},
ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
{ValueType: &pb.Value_IntegerValue{IntegerValue: 123}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_BooleanValue{BooleanValue: false}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_StringValue{StringValue: "short"}, ExcludeFromIndexes: true},
{ValueType: &pb.Value_StringValue{StringValue: strings.Repeat("a", 1503)}, ExcludeFromIndexes: true},
}}},
}
if got := entity.Properties["repeated"]; !proto.Equal(got, want) {
@@ -2562,15 +3172,15 @@ func TestDeferred(t *testing.T) {
entity1 := &pb.Entity{
Key: keyToProto(keys[0]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{1}},
"B": {ValueType: &pb.Value_StringValue{"one"}},
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
},
}
entity2 := &pb.Entity{
Key: keyToProto(keys[1]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{2}},
"B": {ValueType: &pb.Value_StringValue{"two"}},
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
},
}
@@ -2630,19 +3240,94 @@ func TestDeferred(t *testing.T) {
for _, e := range dst {
if e.A == 1 {
if e.B != "one" {
t.Fatalf("unexpected entity %#v", e)
t.Fatalf("unexpected entity %+v", e)
}
} else if e.A == 2 {
if e.B != "two" {
t.Fatalf("unexpected entity %#v", e)
t.Fatalf("unexpected entity %+v", e)
}
} else {
t.Fatalf("unexpected entity %#v", e)
t.Fatalf("unexpected entity %+v", e)
}
}
}
type KeyLoaderEnt struct {
A int
K *Key
}
func (e *KeyLoaderEnt) Load(p []Property) error {
e.A = 2
return nil
}
func (e *KeyLoaderEnt) LoadKey(k *Key) error {
e.K = k
return nil
}
func (e *KeyLoaderEnt) Save() ([]Property, error) {
return []Property{{Name: "A", Value: int64(3)}}, nil
}
func TestKeyLoaderEndToEnd(t *testing.T) {
keys := []*Key{
NameKey("testKind", "first", nil),
NameKey("testKind", "second", nil),
}
entity1 := &pb.Entity{
Key: keyToProto(keys[0]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
},
}
entity2 := &pb.Entity{
Key: keyToProto(keys[1]),
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
},
}
fakeClient := &fakeDatastoreClient{
lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) {
return &pb.LookupResponse{
Found: []*pb.EntityResult{
{
Entity: entity1,
Version: 1,
},
{
Entity: entity2,
Version: 1,
},
},
}, nil
},
}
client := &Client{
client: fakeClient,
}
ctx := context.Background()
dst := make([]*KeyLoaderEnt, len(keys))
err := client.GetMulti(ctx, keys, dst)
if err != nil {
t.Fatalf("client.Get: %v", err)
}
for i := range dst {
if !reflect.DeepEqual(dst[i].K, keys[i]) {
t.Fatalf("unexpected entity %d to have key %+v, got %+v", i, keys[i], dst[i].K)
}
}
}
func TestDeferredMissing(t *testing.T) {
type Ent struct {
A int
@@ -2722,12 +3407,14 @@ func TestDeferredMissing(t *testing.T) {
for _, e := range dst {
if e.A != 0 || e.B != "" {
t.Fatalf("unexpected entity %#v", e)
t.Fatalf("unexpected entity %+v", e)
}
}
}
type fakeDatastoreClient struct {
pb.DatastoreClient
// Optional handlers for the datastore methods.
// Any handlers left undefined will return an error.
lookup func(*pb.LookupRequest) (*pb.LookupResponse, error)
+34
View File
@@ -319,6 +319,40 @@ Example code:
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
+70 -9
View File
@@ -123,7 +123,16 @@ func (l *propertyLoader) loadOneElement(codec fields.List, structValue reflect.V
return "cannot set struct field"
}
var err error
// 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.Struct {
codec, err = structCache.Fields(field.Type)
if err != nil {
@@ -143,6 +152,17 @@ func (l *propertyLoader) loadOneElement(codec fields.List, structValue reflect.V
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 {
@@ -181,10 +201,50 @@ func (l *propertyLoader) loadOneElement(codec fields.List, structValue reflect.V
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 {
pValue := p.Value
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x, ok := pValue.(int64)
@@ -267,12 +327,6 @@ func setVal(v reflect.Value, p Property) string {
if !ok {
return typeMismatchReason(p, v)
}
// Check if v implements PropertyLoadSaver.
if _, ok := v.Interface().(PropertyLoadSaver); ok {
return fmt.Sprintf("datastore: PropertyLoadSaver methods must be implemented on a pointer to %T.", v.Interface())
}
err := loadEntity(v.Addr().Interface(), ent)
if err != nil {
return err.Error()
@@ -320,7 +374,14 @@ func loadEntityProto(dst interface{}, src *pb.Entity) error {
func loadEntity(dst interface{}, ent *Entity) error {
if pls, ok := dst.(PropertyLoadSaver); ok {
return pls.Load(ent.Properties)
err := pls.Load(ent.Properties)
if err != nil {
return err
}
if e, ok := dst.(KeyLoader); ok {
err = e.LoadKey(ent.Key)
}
return err
}
return loadEntityToStruct(dst, ent)
}
+349 -104
View File
@@ -84,71 +84,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",
@@ -193,71 +193,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 +267,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 +306,63 @@ func TestLoadEntityNested(t *testing.T) {
},
},
&NestedSliceOfSimple{
want: &NestedSliceOfSimple{
A: []Simple{Simple{I: 3}, Simple{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 +372,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",
@@ -433,49 +433,49 @@ func TestAlreadyPopulatedDst(t *testing.T) {
want interface{}
}{
{
"simple already populated, nil properties",
&pb.Entity{
desc: "simple already populated, nil properties",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"I": {ValueType: &pb.Value_NullValue{}},
},
},
&Simple{
dst: &Simple{
I: 12,
},
&Simple{},
want: &Simple{},
},
{
"nested structs already populated",
&pb.Entity{
desc: "nested structs already populated",
src: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"SS": {ValueType: &pb.Value_StringValue{"world"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "world"}},
},
},
&SimpleTwoFields{S: "hello" /* SS: "" */},
&SimpleTwoFields{S: "hello", SS: "world"},
dst: &SimpleTwoFields{S: "hello" /* SS: "" */},
want: &SimpleTwoFields{S: "hello", SS: "world"},
},
{
"nested structs already populated, pValues nil",
&pb.Entity{
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{"ss hello"}},
"SS": {ValueType: &pb.Value_StringValue{StringValue: "ss hello"}},
"Nest": {ValueType: &pb.Value_NullValue{}},
"TwiceNest": {ValueType: &pb.Value_EntityValue{
&pb.Entity{
EntityValue: &pb.Entity{
Properties: map[string]*pb.Value{
"A": {ValueType: &pb.Value_NullValue{}},
"I": {ValueType: &pb.Value_IntegerValue{2}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
},
},
}},
"I": {ValueType: &pb.Value_IntegerValue{5}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
},
},
&NestedStructPtrs{
dst: &NestedStructPtrs{
&SimpleTwoFields{S: "hello" /* SS: "" */},
&SimpleTwoFields{ /* S: "" */ SS: "twice hello"},
&NestedSimple2{
@@ -484,7 +484,7 @@ func TestAlreadyPopulatedDst(t *testing.T) {
},
0,
},
&NestedStructPtrs{
want: &NestedStructPtrs{
&SimpleTwoFields{ /* S: "" */ SS: "ss hello"},
nil,
&NestedSimple2{
@@ -508,3 +508,248 @@ func TestAlreadyPopulatedDst(t *testing.T) {
}
}
}
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 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: "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 {
t.Errorf("loadEntityProto: %s: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(tc.want, tc.dst) {
t.Errorf("%s: compare:\ngot: %+v\nwant: %+v", tc.desc, tc.dst, tc.want)
}
}
}
+63
View File
@@ -82,6 +82,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
@@ -277,3 +285,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
}
+19 -11
View File
@@ -32,7 +32,7 @@ var (
Path: []*pb.Key_PathElement{
{
Kind: "Gopher",
IdType: &pb.Key_PathElement_Id{6},
IdType: &pb.Key_PathElement_Id{Id: 6},
},
},
}
@@ -40,11 +40,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 +66,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 +82,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 +91,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.
},
},
@@ -507,12 +507,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{}
+67 -25
View File
@@ -19,6 +19,7 @@ import (
"fmt"
"reflect"
"time"
"unicode/utf8"
timepb "github.com/golang/protobuf/ptypes/timestamp"
pb "google.golang.org/genproto/googleapis/datastore/v1"
@@ -57,8 +58,15 @@ func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect
return nil
}
// Check if v implements PropertyLoadSaver.
pls, isPLS := v.Interface().(PropertyLoadSaver)
// 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) {
case *Key, time.Time, GeoPoint:
@@ -89,19 +97,12 @@ func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect
v = v.Elem()
fallthrough
case reflect.Struct:
if isPLS {
subProps, err := pls.Save()
if err != nil {
return err
}
p.Value = &Entity{Properties: subProps}
break
}
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)
}
@@ -133,6 +134,44 @@ func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect
return nil
}
// 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 {
@@ -290,33 +329,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,
}}
@@ -324,7 +366,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()),
}}
@@ -332,13 +374,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{e}
val.ValueType = &pb.Value_EntityValue{EntityValue: e}
case []interface{}:
arr := make([]*pb.Value, 0, len(v))
for i, v := range v {
@@ -348,7 +390,7 @@ 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
+35 -35
View File
@@ -71,8 +71,8 @@ func TestSaveEntityNested(t *testing.T) {
want *pb.Entity
}{
{
"nested entity with key",
&NestedWithKey{
desc: "nested entity with key",
src: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
@@ -80,17 +80,17 @@ func TestSaveEntityNested(t *testing.T) {
K: testKey1a,
},
},
testKey0,
&pb.Entity{
key: testKey0,
want: &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}},
},
},
}},
@@ -98,8 +98,8 @@ func TestSaveEntityNested(t *testing.T) {
},
},
{
"nested entity with incomplete key",
&NestedWithKey{
desc: "nested entity with incomplete key",
src: &NestedWithKey{
Y: "yyy",
N: WithKey{
X: "two",
@@ -107,17 +107,17 @@ func TestSaveEntityNested(t *testing.T) {
K: incompleteKey,
},
},
testKey0,
&pb.Entity{
key: testKey0,
want: &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(incompleteKey),
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}},
},
},
}},
@@ -125,24 +125,24 @@ func TestSaveEntityNested(t *testing.T) {
},
},
{
"nested entity without key",
&NestedWithoutKey{
desc: "nested entity without key",
src: &NestedWithoutKey{
Y: "yyy",
N: WithoutKey{
X: "two",
I: 2,
},
},
testKey0,
&pb.Entity{
key: testKey0,
want: &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{
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}},
},
},
}},
@@ -150,31 +150,31 @@ func TestSaveEntityNested(t *testing.T) {
},
},
{
"key at top level",
&WithKey{
desc: "key at top level",
src: &WithKey{
X: "three",
I: 3,
K: testKey0,
},
testKey0,
&pb.Entity{
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"X": {ValueType: &pb.Value_StringValue{"three"}},
"I": {ValueType: &pb.Value_IntegerValue{3}},
"X": {ValueType: &pb.Value_StringValue{StringValue: "three"}},
"I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
},
},
},
{
"nested unexported anonymous struct field",
&UnexpAnonym{
desc: "nested unexported anonymous struct field",
src: &UnexpAnonym{
a{S: "hello"},
},
testKey0,
&pb.Entity{
key: testKey0,
want: &pb.Entity{
Key: keyToProto(testKey0),
Properties: map[string]*pb.Value{
"S": {ValueType: &pb.Value_StringValue{"hello"}},
"S": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
},
},
},
+3 -3
View File
@@ -150,7 +150,7 @@ func (t *Transaction) Commit() (*Commit, error) {
}
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,
}
@@ -201,7 +201,7 @@ func (t *Transaction) Rollback() error {
// or modified by this transaction.
func (t *Transaction) Get(key *Key, dst interface{}) error {
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)
if me, ok := err.(MultiError); ok {
@@ -216,7 +216,7 @@ func (t *Transaction) GetMulti(keys []*Key, dst interface{}) error {
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)
}