| // Copyright 2011 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 ( |
| "errors" |
| "fmt" |
| "reflect" |
| |
| "github.com/golang/protobuf/proto" |
| "golang.org/x/net/context" |
| |
| "google.golang.org/appengine" |
| "google.golang.org/appengine/internal" |
| pb "google.golang.org/appengine/internal/datastore" |
| ) |
| |
| var ( |
| // ErrInvalidEntityType is returned when functions like Get or Next are |
| // passed a dst or src argument of invalid type. |
| ErrInvalidEntityType = errors.New("datastore: invalid entity type") |
| // ErrInvalidKey is returned when an invalid key is presented. |
| ErrInvalidKey = errors.New("datastore: invalid key") |
| // ErrNoSuchEntity is returned when no entity was found for a given key. |
| ErrNoSuchEntity = errors.New("datastore: no such entity") |
| ) |
| |
| // ErrFieldMismatch is returned when a field is to be loaded into a different |
| // type than the one it was stored from, or when a field is missing or |
| // unexported in the destination struct. |
| // StructType is the type of the struct pointed to by the destination argument |
| // passed to Get or to Iterator.Next. |
| type ErrFieldMismatch struct { |
| StructType reflect.Type |
| FieldName string |
| Reason string |
| } |
| |
| func (e *ErrFieldMismatch) Error() string { |
| return fmt.Sprintf("datastore: cannot load field %q into a %q: %s", |
| e.FieldName, e.StructType, e.Reason) |
| } |
| |
| // protoToKey converts a Reference proto to a *Key. |
| func protoToKey(r *pb.Reference) (k *Key, err error) { |
| appID := r.GetApp() |
| namespace := r.GetNameSpace() |
| for _, e := range r.Path.Element { |
| k = &Key{ |
| kind: e.GetType(), |
| stringID: e.GetName(), |
| intID: e.GetId(), |
| parent: k, |
| appID: appID, |
| namespace: namespace, |
| } |
| if !k.valid() { |
| return nil, ErrInvalidKey |
| } |
| } |
| return |
| } |
| |
| // keyToProto converts a *Key to a Reference proto. |
| func keyToProto(defaultAppID string, k *Key) *pb.Reference { |
| appID := k.appID |
| if appID == "" { |
| appID = defaultAppID |
| } |
| n := 0 |
| for i := k; i != nil; i = i.parent { |
| n++ |
| } |
| e := make([]*pb.Path_Element, n) |
| for i := k; i != nil; i = i.parent { |
| n-- |
| e[n] = &pb.Path_Element{ |
| Type: &i.kind, |
| } |
| // At most one of {Name,Id} should be set. |
| // Neither will be set for incomplete keys. |
| if i.stringID != "" { |
| e[n].Name = &i.stringID |
| } else if i.intID != 0 { |
| e[n].Id = &i.intID |
| } |
| } |
| var namespace *string |
| if k.namespace != "" { |
| namespace = proto.String(k.namespace) |
| } |
| return &pb.Reference{ |
| App: proto.String(appID), |
| NameSpace: namespace, |
| Path: &pb.Path{ |
| Element: e, |
| }, |
| } |
| } |
| |
| // multiKeyToProto is a batch version of keyToProto. |
| func multiKeyToProto(appID string, key []*Key) []*pb.Reference { |
| ret := make([]*pb.Reference, len(key)) |
| for i, k := range key { |
| ret[i] = keyToProto(appID, k) |
| } |
| return ret |
| } |
| |
| // multiValid is a batch version of Key.valid. It returns an error, not a |
| // []bool. |
| func multiValid(key []*Key) error { |
| invalid := false |
| for _, k := range key { |
| if !k.valid() { |
| invalid = true |
| break |
| } |
| } |
| if !invalid { |
| return nil |
| } |
| err := make(appengine.MultiError, len(key)) |
| for i, k := range key { |
| if !k.valid() { |
| err[i] = ErrInvalidKey |
| } |
| } |
| return err |
| } |
| |
| // It's unfortunate that the two semantically equivalent concepts pb.Reference |
| // and pb.PropertyValue_ReferenceValue aren't the same type. For example, the |
| // two have different protobuf field numbers. |
| |
| // referenceValueToKey is the same as protoToKey except the input is a |
| // PropertyValue_ReferenceValue instead of a Reference. |
| func referenceValueToKey(r *pb.PropertyValue_ReferenceValue) (k *Key, err error) { |
| appID := r.GetApp() |
| namespace := r.GetNameSpace() |
| for _, e := range r.Pathelement { |
| k = &Key{ |
| kind: e.GetType(), |
| stringID: e.GetName(), |
| intID: e.GetId(), |
| parent: k, |
| appID: appID, |
| namespace: namespace, |
| } |
| if !k.valid() { |
| return nil, ErrInvalidKey |
| } |
| } |
| return |
| } |
| |
| // keyToReferenceValue is the same as keyToProto except the output is a |
| // PropertyValue_ReferenceValue instead of a Reference. |
| func keyToReferenceValue(defaultAppID string, k *Key) *pb.PropertyValue_ReferenceValue { |
| ref := keyToProto(defaultAppID, k) |
| pe := make([]*pb.PropertyValue_ReferenceValue_PathElement, len(ref.Path.Element)) |
| for i, e := range ref.Path.Element { |
| pe[i] = &pb.PropertyValue_ReferenceValue_PathElement{ |
| Type: e.Type, |
| Id: e.Id, |
| Name: e.Name, |
| } |
| } |
| return &pb.PropertyValue_ReferenceValue{ |
| App: ref.App, |
| NameSpace: ref.NameSpace, |
| Pathelement: pe, |
| } |
| } |
| |
| type multiArgType int |
| |
| const ( |
| multiArgTypeInvalid multiArgType = iota |
| multiArgTypePropertyLoadSaver |
| multiArgTypeStruct |
| multiArgTypeStructPtr |
| multiArgTypeInterface |
| ) |
| |
| // checkMultiArg checks that v has type []S, []*S, []I, or []P, for some struct |
| // type S, for some interface type I, or some non-interface non-pointer type P |
| // such that P or *P implements PropertyLoadSaver. |
| // |
| // It returns what category the slice's elements are, and the reflect.Type |
| // that represents S, I or P. |
| // |
| // As a special case, PropertyList is an invalid type for v. |
| func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) { |
| if v.Kind() != reflect.Slice { |
| return multiArgTypeInvalid, nil |
| } |
| if v.Type() == typeOfPropertyList { |
| return multiArgTypeInvalid, nil |
| } |
| elemType = v.Type().Elem() |
| if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) { |
| return multiArgTypePropertyLoadSaver, elemType |
| } |
| switch elemType.Kind() { |
| case reflect.Struct: |
| return multiArgTypeStruct, elemType |
| case reflect.Interface: |
| return multiArgTypeInterface, elemType |
| case reflect.Ptr: |
| elemType = elemType.Elem() |
| if elemType.Kind() == reflect.Struct { |
| return multiArgTypeStructPtr, elemType |
| } |
| } |
| return multiArgTypeInvalid, nil |
| } |
| |
| // Get loads the entity stored for k into dst, which must be a struct pointer |
| // or implement PropertyLoadSaver. If there is no such entity for the key, Get |
| // returns ErrNoSuchEntity. |
| // |
| // The values of dst's unmatched struct fields are not modified, and matching |
| // slice-typed fields are not reset before appending to them. In particular, it |
| // is recommended to pass a pointer to a zero valued struct on each Get call. |
| // |
| // ErrFieldMismatch is returned when a field is to be loaded into a different |
| // type than the one it was stored from, or when a field is missing or |
| // unexported in the destination struct. ErrFieldMismatch is only returned if |
| // dst is a struct pointer. |
| func Get(c context.Context, key *Key, dst interface{}) error { |
| if dst == nil { // GetMulti catches nil interface; we need to catch nil ptr here |
| return ErrInvalidEntityType |
| } |
| err := GetMulti(c, []*Key{key}, []interface{}{dst}) |
| if me, ok := err.(appengine.MultiError); ok { |
| return me[0] |
| } |
| return err |
| } |
| |
| // GetMulti is a batch version of Get. |
| // |
| // dst must be a []S, []*S, []I or []P, for some struct type S, some interface |
| // type I, or some non-interface non-pointer type P such that P or *P |
| // implements PropertyLoadSaver. If an []I, each element must be a valid dst |
| // for Get: it must be a struct pointer or implement PropertyLoadSaver. |
| // |
| // As a special case, PropertyList is an invalid type for dst, even though a |
| // PropertyList is a slice of structs. It is treated as invalid to avoid being |
| // mistakenly passed when []PropertyList was intended. |
| func GetMulti(c context.Context, key []*Key, dst interface{}) error { |
| v := reflect.ValueOf(dst) |
| multiArgType, _ := checkMultiArg(v) |
| if multiArgType == multiArgTypeInvalid { |
| return errors.New("datastore: dst has invalid type") |
| } |
| if len(key) != v.Len() { |
| return errors.New("datastore: key and dst slices have different length") |
| } |
| if len(key) == 0 { |
| return nil |
| } |
| if err := multiValid(key); err != nil { |
| return err |
| } |
| req := &pb.GetRequest{ |
| Key: multiKeyToProto(internal.FullyQualifiedAppID(c), key), |
| } |
| res := &pb.GetResponse{} |
| if err := internal.Call(c, "datastore_v3", "Get", req, res); err != nil { |
| return err |
| } |
| if len(key) != len(res.Entity) { |
| return errors.New("datastore: internal error: server returned the wrong number of entities") |
| } |
| multiErr, any := make(appengine.MultiError, len(key)), false |
| for i, e := range res.Entity { |
| if e.Entity == nil { |
| multiErr[i] = ErrNoSuchEntity |
| } else { |
| elem := v.Index(i) |
| if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { |
| elem = elem.Addr() |
| } |
| if multiArgType == multiArgTypeStructPtr && elem.IsNil() { |
| elem.Set(reflect.New(elem.Type().Elem())) |
| } |
| multiErr[i] = loadEntity(elem.Interface(), e.Entity) |
| } |
| if multiErr[i] != nil { |
| any = true |
| } |
| } |
| if any { |
| return multiErr |
| } |
| return nil |
| } |
| |
| // Put saves the entity src into the datastore with key k. src must be a struct |
| // pointer or implement PropertyLoadSaver; if a struct pointer then any |
| // unexported fields of that struct will be skipped. If k is an incomplete key, |
| // the returned key will be a unique key generated by the datastore. |
| func Put(c context.Context, key *Key, src interface{}) (*Key, error) { |
| k, err := PutMulti(c, []*Key{key}, []interface{}{src}) |
| if err != nil { |
| if me, ok := err.(appengine.MultiError); ok { |
| return nil, me[0] |
| } |
| return nil, err |
| } |
| return k[0], nil |
| } |
| |
| // PutMulti is a batch version of Put. |
| // |
| // src must satisfy the same conditions as the dst argument to GetMulti. |
| func PutMulti(c context.Context, key []*Key, src interface{}) ([]*Key, error) { |
| v := reflect.ValueOf(src) |
| multiArgType, _ := checkMultiArg(v) |
| if multiArgType == multiArgTypeInvalid { |
| return nil, errors.New("datastore: src has invalid type") |
| } |
| if len(key) != v.Len() { |
| return nil, errors.New("datastore: key and src slices have different length") |
| } |
| if len(key) == 0 { |
| return nil, nil |
| } |
| appID := internal.FullyQualifiedAppID(c) |
| if err := multiValid(key); err != nil { |
| return nil, err |
| } |
| req := &pb.PutRequest{} |
| for i := range key { |
| elem := v.Index(i) |
| if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { |
| elem = elem.Addr() |
| } |
| sProto, err := saveEntity(appID, key[i], elem.Interface()) |
| if err != nil { |
| return nil, err |
| } |
| req.Entity = append(req.Entity, sProto) |
| } |
| res := &pb.PutResponse{} |
| if err := internal.Call(c, "datastore_v3", "Put", req, res); err != nil { |
| return nil, err |
| } |
| if len(key) != len(res.Key) { |
| return nil, errors.New("datastore: internal error: server returned the wrong number of keys") |
| } |
| ret := make([]*Key, len(key)) |
| for i := range ret { |
| var err error |
| ret[i], err = protoToKey(res.Key[i]) |
| if err != nil || ret[i].Incomplete() { |
| return nil, errors.New("datastore: internal error: server returned an invalid key") |
| } |
| } |
| return ret, nil |
| } |
| |
| // Delete deletes the entity for the given key. |
| func Delete(c context.Context, key *Key) error { |
| err := DeleteMulti(c, []*Key{key}) |
| if me, ok := err.(appengine.MultiError); ok { |
| return me[0] |
| } |
| return err |
| } |
| |
| // DeleteMulti is a batch version of Delete. |
| func DeleteMulti(c context.Context, key []*Key) error { |
| if len(key) == 0 { |
| return nil |
| } |
| if err := multiValid(key); err != nil { |
| return err |
| } |
| req := &pb.DeleteRequest{ |
| Key: multiKeyToProto(internal.FullyQualifiedAppID(c), key), |
| } |
| res := &pb.DeleteResponse{} |
| return internal.Call(c, "datastore_v3", "Delete", req, res) |
| } |
| |
| func namespaceMod(m proto.Message, namespace string) { |
| // pb.Query is the only type that has a name_space field. |
| // All other namespace support in datastore is in the keys. |
| switch m := m.(type) { |
| case *pb.Query: |
| if m.NameSpace == nil { |
| m.NameSpace = &namespace |
| } |
| } |
| } |
| |
| func init() { |
| internal.NamespaceMods["datastore_v3"] = namespaceMod |
| internal.RegisterErrorCodeMap("datastore_v3", pb.Error_ErrorCode_name) |
| internal.RegisterTimeoutErrorCode("datastore_v3", int32(pb.Error_TIMEOUT)) |
| } |