vendor: update all dependencies
This commit is contained in:
+15
-11
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user