datastore: support for loading nested entity values

This change is an almost-copy of a change submitted to
cloud.google.com/go/datastore

Change-Id: Idbfc200b7a3a76603e02527d9de4e93f3ccb648c
diff --git a/datastore/load.go b/datastore/load.go
index b1cac60..f69dad4 100644
--- a/datastore/load.go
+++ b/datastore/load.go
@@ -10,6 +10,7 @@
 	"strings"
 	"time"
 
+	"github.com/golang/protobuf/proto"
 	"google.golang.org/appengine"
 	pb "google.golang.org/appengine/internal/datastore"
 )
@@ -21,6 +22,7 @@
 	typeOfGeoPoint   = reflect.TypeOf(appengine.GeoPoint{})
 	typeOfTime       = reflect.TypeOf(time.Time{})
 	typeOfKeyPtr     = reflect.TypeOf(&Key{})
+	typeOfEntityPtr  = reflect.TypeOf(&Entity{})
 )
 
 // typeMismatchReason returns a string explaining why the property p could not
@@ -146,6 +148,8 @@
 			meaning = pb.Property_GEORSS_POINT
 		case typeOfTime:
 			meaning = pb.Property_GD_WHEN
+		case typeOfEntityPtr:
+			meaning = pb.Property_ENTITY_PROTO
 		}
 		var err error
 		pValue, err = propValue(iv.value, meaning)
@@ -154,7 +158,7 @@
 		}
 	}
 
-	if errReason := setVal(pValue, v); errReason != "" {
+	if errReason := setVal(v, pValue); errReason != "" {
 		// Set the slice back to its zero value.
 		if slice.IsValid() {
 			slice.Set(reflect.Zero(slice.Type()))
@@ -169,8 +173,8 @@
 	return ""
 }
 
-// setVal sets v to the value of Property p.
-func setVal(pValue interface{}, v reflect.Value) string {
+// setVal sets v to the value pValue.
+func setVal(v reflect.Value, pValue interface{}) string {
 	switch v.Kind() {
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		x, ok := pValue.(int64)
@@ -233,7 +237,28 @@
 			}
 			v.Set(reflect.ValueOf(x))
 		default:
-			return typeMismatchReason(pValue, v)
+			ent, ok := pValue.(*Entity)
+			if !ok {
+				return typeMismatchReason(pValue, v)
+			}
+
+			// Recursively load nested struct
+			pls, err := newStructPLS(v.Addr().Interface())
+			if err != nil {
+				return err.Error()
+			}
+
+			// if ent has a Key value and our struct has a Key field,
+			// load the Entity's Key value into the Key field on the struct.
+			if ent.Key != nil && pls.codec.keyField != -1 {
+
+				pls.v.Field(pls.codec.keyField).Set(reflect.ValueOf(ent.Key))
+			}
+
+			err = pls.Load(ent.Properties)
+			if err != nil {
+				return err.Error()
+			}
 		}
 	case reflect.Slice:
 		x, ok := pValue.([]byte)
@@ -255,7 +280,8 @@
 	return ""
 }
 
-// initField is similar to reflect's Value.FieldByIndex, but
+// initField is similar to reflect's Value.FieldByIndex, in that it
+// returns the nested struct field corresponding to index, but it
 // initialises any nil pointers encountered when traversing the structure.
 func initField(val reflect.Value, index []int) reflect.Value {
 	for _, i := range index[:len(index)-1] {
@@ -272,14 +298,14 @@
 
 // loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
 func loadEntity(dst interface{}, src *pb.EntityProto) (err error) {
-	props, err := protoToProperties(src)
+	ent, err := protoToEntity(src)
 	if err != nil {
 		return err
 	}
 	if e, ok := dst.(PropertyLoadSaver); ok {
-		return e.Load(props)
+		return e.Load(ent.Properties)
 	}
-	return LoadStruct(dst, props)
+	return LoadStruct(dst, ent.Properties)
 }
 
 func (s structPLS) Load(props []Property) error {
@@ -303,9 +329,9 @@
 	return nil
 }
 
-func protoToProperties(src *pb.EntityProto) ([]Property, error) {
+func protoToEntity(src *pb.EntityProto) (*Entity, error) {
 	props, rawProps := src.Property, src.RawProperty
-	out := make([]Property, 0, len(props)+len(rawProps))
+	outProps := make([]Property, 0, len(props)+len(rawProps))
 	for {
 		var (
 			x       *pb.Property
@@ -330,14 +356,21 @@
 				return nil, err
 			}
 		}
-		out = append(out, Property{
+		outProps = append(outProps, Property{
 			Name:     x.GetName(),
 			Value:    value,
 			NoIndex:  noIndex,
 			Multiple: x.GetMultiple(),
 		})
 	}
-	return out, nil
+
+	var key *Key
+	if src.Key != nil {
+		// Ignore any error, since nested entity values
+		// are allowed to have an invalid key.
+		key, _ = protoToKey(src.Key)
+	}
+	return &Entity{key, outProps}, nil
 }
 
 // propValue returns a Go value that combines the raw PropertyValue with a
@@ -359,6 +392,13 @@
 			return appengine.BlobKey(*v.StringValue), nil
 		} else if m == pb.Property_BYTESTRING {
 			return ByteString(*v.StringValue), nil
+		} else if m == pb.Property_ENTITY_PROTO {
+			var ent pb.EntityProto
+			err := proto.Unmarshal([]byte(*v.StringValue), &ent)
+			if err != nil {
+				return nil, err
+			}
+			return protoToEntity(&ent)
 		} else {
 			return *v.StringValue, nil
 		}
diff --git a/datastore/load_test.go b/datastore/load_test.go
new file mode 100644
index 0000000..46029bb
--- /dev/null
+++ b/datastore/load_test.go
@@ -0,0 +1,656 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package datastore
+
+import (
+	"reflect"
+	"testing"
+
+	proto "github.com/golang/protobuf/proto"
+	pb "google.golang.org/appengine/internal/datastore"
+)
+
+type Simple struct {
+	I int64
+}
+
+type SimpleWithTag struct {
+	I int64 `datastore:"II"`
+}
+
+type NestedSimpleWithTag struct {
+	A SimpleWithTag `datastore:"AA"`
+}
+
+type NestedSliceOfSimple struct {
+	A []Simple
+}
+
+type SimpleTwoFields struct {
+	S  string
+	SS string
+}
+
+type NestedSimpleAnonymous struct {
+	Simple
+	X string
+}
+
+type NestedSimple struct {
+	A Simple
+	I int64
+}
+
+type NestedSimple1 struct {
+	A Simple
+	X string
+}
+
+type NestedSimple2X struct {
+	AA NestedSimple
+	A  SimpleTwoFields
+	S  string
+}
+
+type BDotB struct {
+	B string `datastore:"B.B"`
+}
+
+type ABDotB struct {
+	A BDotB
+}
+
+type MultiAnonymous struct {
+	Simple
+	SimpleTwoFields
+	X string
+}
+
+var (
+	// these values need to be addressable
+	testString2 = "two"
+	testString3 = "three"
+	testInt64   = int64(2)
+
+	fieldNameI         = "I"
+	fieldNameX         = "X"
+	fieldNameS         = "S"
+	fieldNameSS        = "SS"
+	fieldNameADotI     = "A.I"
+	fieldNameAADotII   = "AA.II"
+	fieldNameADotBDotB = "A.B.B"
+)
+
+func TestLoadEntityNestedLegacy(t *testing.T) {
+	testCases := []struct {
+		desc string
+		src  *pb.EntityProto
+		want interface{}
+	}{
+		{
+			"nested",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameX,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameADotI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+				},
+			},
+			&NestedSimple1{
+				A: Simple{I: testInt64},
+				X: testString2,
+			},
+		},
+		{
+			"nested with tag",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameAADotII,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+				},
+			},
+			&NestedSimpleWithTag{
+				A: SimpleWithTag{I: testInt64},
+			},
+		},
+		{
+			"nested with anonymous struct field",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameX,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+				},
+			},
+			&NestedSimpleAnonymous{
+				Simple: Simple{I: testInt64},
+				X:      testString2,
+			},
+		},
+		{
+			"nested with dotted field tag",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameADotBDotB,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+				},
+			},
+			&ABDotB{
+				A: BDotB{
+					B: testString2,
+				},
+			},
+		},
+		{
+			"nested with dotted field tag",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameS,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameSS,
+						Value: &pb.PropertyValue{
+							StringValue: &testString3,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameX,
+						Value: &pb.PropertyValue{
+							StringValue: &testString3,
+						},
+					},
+				},
+			},
+			&MultiAnonymous{
+				Simple:          Simple{I: testInt64},
+				SimpleTwoFields: SimpleTwoFields{S: "two", SS: "three"},
+				X:               "three",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
+		err := loadEntity(dst, tc.src)
+		if err != nil {
+			t.Errorf("loadEntity: %s: %v", tc.desc, err)
+			continue
+		}
+
+		if !reflect.DeepEqual(tc.want, dst) {
+			t.Errorf("%s: compare:\ngot:  %#v\nwant: %#v", tc.desc, dst, tc.want)
+		}
+	}
+}
+
+type WithKey struct {
+	X string
+	I int64
+	K *Key `datastore:"__key__"`
+}
+
+type NestedWithKey struct {
+	N WithKey
+	Y string
+}
+
+var (
+	incompleteKey = newKey("", nil)
+	invalidKey    = newKey("s", incompleteKey)
+
+	// these values need to be addressable
+	fieldNameA     = "A"
+	fieldNameK     = "K"
+	fieldNameN     = "N"
+	fieldNameY     = "Y"
+	fieldNameAA    = "AA"
+	fieldNameII    = "II"
+	fieldNameBDotB = "B.B"
+
+	entityProtoMeaning = pb.Property_ENTITY_PROTO
+
+	TRUE  = true
+	FALSE = false
+)
+
+var (
+	simpleEntityProto, nestedSimpleEntityProto,
+	simpleTwoFieldsEntityProto, simpleWithTagEntityProto,
+	bDotBEntityProto, withKeyEntityProto string
+)
+
+func init() {
+	// simpleEntityProto corresponds to:
+	// Simple{I: testInt64}
+	simpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", incompleteKey),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name: &fieldNameI,
+				Value: &pb.PropertyValue{
+					Int64Value: &testInt64,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	simpleEntityProto = string(simpleEntityProtob)
+
+	// nestedSimpleEntityProto corresponds to:
+	// NestedSimple{
+	// 	A: Simple{I: testInt64},
+	// 	I: testInt64,
+	// }
+	nestedSimpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", incompleteKey),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name:    &fieldNameA,
+				Meaning: &entityProtoMeaning,
+				Value: &pb.PropertyValue{
+					StringValue: &simpleEntityProto,
+				},
+				Multiple: &FALSE,
+			},
+			&pb.Property{
+				Name:    &fieldNameI,
+				Meaning: &entityProtoMeaning,
+				Value: &pb.PropertyValue{
+					Int64Value: &testInt64,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	nestedSimpleEntityProto = string(nestedSimpleEntityProtob)
+
+	// simpleTwoFieldsEntityProto corresponds to:
+	// SimpleTwoFields{S: testString2, SS: testString3}
+	simpleTwoFieldsEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", incompleteKey),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name: &fieldNameS,
+				Value: &pb.PropertyValue{
+					StringValue: &testString2,
+				},
+				Multiple: &FALSE,
+			},
+			&pb.Property{
+				Name: &fieldNameSS,
+				Value: &pb.PropertyValue{
+					StringValue: &testString3,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	simpleTwoFieldsEntityProto = string(simpleTwoFieldsEntityProtob)
+
+	// simpleWithTagEntityProto corresponds to:
+	// SimpleWithTag{I: testInt64}
+	simpleWithTagEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", incompleteKey),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name: &fieldNameII,
+				Value: &pb.PropertyValue{
+					Int64Value: &testInt64,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	simpleWithTagEntityProto = string(simpleWithTagEntityProtob)
+
+	// bDotBEntityProto corresponds to:
+	// BDotB{
+	// 	B: testString2,
+	// }
+	bDotBEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", incompleteKey),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name: &fieldNameBDotB,
+				Value: &pb.PropertyValue{
+					StringValue: &testString2,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	bDotBEntityProto = string(bDotBEntityProtob)
+
+	// withKeyEntityProto corresponds to:
+	// WithKey{
+	// 	X: testString3,
+	// 	I: testInt64,
+	// 	K: testKey1a,
+	// }
+	withKeyEntityProtob, err := proto.Marshal(&pb.EntityProto{
+		Key: keyToProto("", testKey1a),
+		Property: []*pb.Property{
+			&pb.Property{
+				Name: &fieldNameX,
+				Value: &pb.PropertyValue{
+					StringValue: &testString3,
+				},
+				Multiple: &FALSE,
+			},
+			&pb.Property{
+				Name: &fieldNameI,
+				Value: &pb.PropertyValue{
+					Int64Value: &testInt64,
+				},
+				Multiple: &FALSE,
+			},
+		},
+		EntityGroup: &pb.Path{},
+	})
+	if err != nil {
+		panic(err)
+	}
+	withKeyEntityProto = string(withKeyEntityProtob)
+
+}
+
+func TestLoadEntityNested(t *testing.T) {
+	testCases := []struct {
+		desc string
+		src  *pb.EntityProto
+		want interface{}
+	}{
+		{
+			"nested basic",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name:    &fieldNameA,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &simpleEntityProto,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+				},
+			},
+			&NestedSimple{
+				A: Simple{I: 2},
+				I: 2,
+			},
+		},
+		{
+			"nested with struct tags",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name:    &fieldNameAA,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &simpleWithTagEntityProto,
+						},
+					},
+				},
+			},
+			&NestedSimpleWithTag{
+				A: SimpleWithTag{I: testInt64},
+			},
+		},
+		{
+			"nested 2x",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name:    &fieldNameAA,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &nestedSimpleEntityProto,
+						},
+					},
+					&pb.Property{
+						Name:    &fieldNameA,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &simpleTwoFieldsEntityProto,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameS,
+						Value: &pb.PropertyValue{
+							StringValue: &testString3,
+						},
+					},
+				},
+			},
+			&NestedSimple2X{
+				AA: NestedSimple{
+					A: Simple{I: testInt64},
+					I: testInt64,
+				},
+				A: SimpleTwoFields{S: testString2, SS: testString3},
+				S: testString3,
+			},
+		},
+		{
+			"nested anonymous",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameX,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+				},
+			},
+			&NestedSimpleAnonymous{
+				Simple: Simple{I: testInt64},
+				X:      testString2,
+			},
+		},
+		{
+			"nested simple with slice",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name:     &fieldNameA,
+						Meaning:  &entityProtoMeaning,
+						Multiple: &TRUE,
+						Value: &pb.PropertyValue{
+							StringValue: &simpleEntityProto,
+						},
+					},
+					&pb.Property{
+						Name:     &fieldNameA,
+						Meaning:  &entityProtoMeaning,
+						Multiple: &TRUE,
+						Value: &pb.PropertyValue{
+							StringValue: &simpleEntityProto,
+						},
+					},
+				},
+			},
+			&NestedSliceOfSimple{
+				A: []Simple{Simple{I: testInt64}, Simple{I: testInt64}},
+			},
+		},
+		{
+			"nested with multiple anonymous fields",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameI,
+						Value: &pb.PropertyValue{
+							Int64Value: &testInt64,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameS,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameSS,
+						Value: &pb.PropertyValue{
+							StringValue: &testString3,
+						},
+					},
+					&pb.Property{
+						Name: &fieldNameX,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+				},
+			},
+			&MultiAnonymous{
+				Simple:          Simple{I: testInt64},
+				SimpleTwoFields: SimpleTwoFields{S: testString2, SS: testString3},
+				X:               testString2,
+			},
+		},
+		{
+			"nested with dotted field tag",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name:    &fieldNameA,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &bDotBEntityProto,
+						},
+					},
+				},
+			},
+			&ABDotB{
+				A: BDotB{
+					B: testString2,
+				},
+			},
+		},
+		{
+			"nested entity with key",
+			&pb.EntityProto{
+				Key: keyToProto("some-app-id", testKey0),
+				Property: []*pb.Property{
+					&pb.Property{
+						Name: &fieldNameY,
+						Value: &pb.PropertyValue{
+							StringValue: &testString2,
+						},
+					},
+					&pb.Property{
+						Name:    &fieldNameN,
+						Meaning: &entityProtoMeaning,
+						Value: &pb.PropertyValue{
+							StringValue: &withKeyEntityProto,
+						},
+					},
+				},
+			},
+			&NestedWithKey{
+				Y: testString2,
+				N: WithKey{
+					X: testString3,
+					I: testInt64,
+					K: testKey1a,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
+		err := loadEntity(dst, tc.src)
+		if err != nil {
+			t.Errorf("loadEntity: %s: %v", tc.desc, err)
+			continue
+		}
+
+		if !reflect.DeepEqual(tc.want, dst) {
+			t.Errorf("%s: compare:\ngot:  %#v\nwant: %#v", tc.desc, dst, tc.want)
+		}
+	}
+}
diff --git a/datastore/prop.go b/datastore/prop.go
index 905bcf0..55ab9fb 100644
--- a/datastore/prop.go
+++ b/datastore/prop.go
@@ -36,6 +36,7 @@
 	//	- appengine.BlobKey
 	//	- appengine.GeoPoint
 	//	- []byte (up to 1 megabyte in length)
+	//	- *Entity (representing a nested struct)
 	// This set is smaller than the set of valid struct field types that the
 	// datastore can load and save. A Property Value cannot be a slice (apart
 	// from []byte); use multiple Properties instead. Also, a Value's type
@@ -63,6 +64,13 @@
 	Multiple bool
 }
 
+// An Entity is the value type for a nested struct.
+// This type is only used for a Property's Value.
+type Entity struct {
+	Key        *Key
+	Properties []Property
+}
+
 // ByteString is a short byte slice (up to 1500 bytes) that can be indexed.
 type ByteString []byte
 
@@ -127,6 +135,10 @@
 	// hasSlice is whether a struct or any of its nested or embedded structs
 	// has a slice-typed field (other than []byte).
 	hasSlice bool
+	// keyField is the index of a *Key field with structTag __key__.
+	// This field is not relevant for the top level struct, only for
+	// nested structs.
+	keyField int
 	// complete is whether the structCodec is complete. An incomplete
 	// structCodec may be encountered when walking a recursive struct.
 	complete bool
@@ -165,6 +177,9 @@
 	}
 	c = &structCodec{
 		fields: make(map[string]fieldCodec),
+		// We initialize keyField to -1 so that the zero-value is not
+		// misinterpreted as index 0.
+		keyField: -1,
 	}
 
 	// Add c to the structCodecs map before we are sure it is good. If t is
@@ -192,13 +207,19 @@
 		for _, t := range tags[1:] {
 			opts[t] = true
 		}
-		if name == "" {
+		switch {
+		case name == "":
 			if !f.Anonymous {
 				name = f.Name
 			}
-		} else if name == "-" {
+		case name == "-":
 			continue
-		} else if !validPropertyName(name) {
+		case name == "__key__":
+			if f.Type != typeOfKeyPtr {
+				return nil, fmt.Errorf("datastore: __key__ field on struct %v is not a *datastore.Key", t)
+			}
+			c.keyField = i
+		case !validPropertyName(name):
 			return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
 		}
 
@@ -266,8 +287,9 @@
 	codec *structCodec
 }
 
-// newStructPLS returns a PropertyLoadSaver for the struct pointer p.
-func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
+// newStructPLS returns a structPLS, which implements the
+// PropertyLoadSaver interface, for the struct pointer p.
+func newStructPLS(p interface{}) (*structPLS, error) {
 	v := reflect.ValueOf(p)
 	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
 		return nil, ErrInvalidEntityType
@@ -277,7 +299,7 @@
 	if err != nil {
 		return nil, err
 	}
-	return structPLS{v, codec}, nil
+	return &structPLS{v, codec}, nil
 }
 
 // LoadStruct loads the properties from p to dst.
diff --git a/datastore/save.go b/datastore/save.go
index d3ecba3..b09e8b3 100644
--- a/datastore/save.go
+++ b/datastore/save.go
@@ -166,7 +166,7 @@
 			if err != nil {
 				return fmt.Errorf("datastore: unsupported struct field: %v", err)
 			}
-			return sub.(structPLS).save(props, name+".", noIndex, multiple)
+			return sub.save(props, name+".", noIndex, multiple)
 		}
 	}
 	if p.Value == nil {