| // 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 ( |
| "bytes" |
| "encoding/base64" |
| "encoding/gob" |
| "errors" |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "github.com/golang/protobuf/proto" |
| "golang.org/x/net/context" |
| |
| "google.golang.org/appengine/internal" |
| pb "google.golang.org/appengine/internal/datastore" |
| ) |
| |
| // Key represents the datastore key for a stored entity, and is immutable. |
| type Key struct { |
| kind string |
| stringID string |
| intID int64 |
| parent *Key |
| appID string |
| namespace string |
| } |
| |
| // Kind returns the key's kind (also known as entity type). |
| func (k *Key) Kind() string { |
| return k.kind |
| } |
| |
| // StringID returns the key's string ID (also known as an entity name or key |
| // name), which may be "". |
| func (k *Key) StringID() string { |
| return k.stringID |
| } |
| |
| // IntID returns the key's integer ID, which may be 0. |
| func (k *Key) IntID() int64 { |
| return k.intID |
| } |
| |
| // Parent returns the key's parent key, which may be nil. |
| func (k *Key) Parent() *Key { |
| return k.parent |
| } |
| |
| // AppID returns the key's application ID. |
| func (k *Key) AppID() string { |
| return k.appID |
| } |
| |
| // Namespace returns the key's namespace. |
| func (k *Key) Namespace() string { |
| return k.namespace |
| } |
| |
| // Incomplete returns whether the key does not refer to a stored entity. |
| // In particular, whether the key has a zero StringID and a zero IntID. |
| func (k *Key) Incomplete() bool { |
| return k.stringID == "" && k.intID == 0 |
| } |
| |
| // valid returns whether the key is valid. |
| func (k *Key) valid() bool { |
| if k == nil { |
| return false |
| } |
| for ; k != nil; k = k.parent { |
| if k.kind == "" || k.appID == "" { |
| return false |
| } |
| if k.stringID != "" && k.intID != 0 { |
| return false |
| } |
| if k.parent != nil { |
| if k.parent.Incomplete() { |
| return false |
| } |
| if k.parent.appID != k.appID || k.parent.namespace != k.namespace { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| // Equal returns whether two keys are equal. |
| func (k *Key) Equal(o *Key) bool { |
| for k != nil && o != nil { |
| if k.kind != o.kind || k.stringID != o.stringID || k.intID != o.intID || k.appID != o.appID || k.namespace != o.namespace { |
| return false |
| } |
| k, o = k.parent, o.parent |
| } |
| return k == o |
| } |
| |
| // root returns the furthest ancestor of a key, which may be itself. |
| func (k *Key) root() *Key { |
| for k.parent != nil { |
| k = k.parent |
| } |
| return k |
| } |
| |
| // marshal marshals the key's string representation to the buffer. |
| func (k *Key) marshal(b *bytes.Buffer) { |
| if k.parent != nil { |
| k.parent.marshal(b) |
| } |
| b.WriteByte('/') |
| b.WriteString(k.kind) |
| b.WriteByte(',') |
| if k.stringID != "" { |
| b.WriteString(k.stringID) |
| } else { |
| b.WriteString(strconv.FormatInt(k.intID, 10)) |
| } |
| } |
| |
| // String returns a string representation of the key. |
| func (k *Key) String() string { |
| if k == nil { |
| return "" |
| } |
| b := bytes.NewBuffer(make([]byte, 0, 512)) |
| k.marshal(b) |
| return b.String() |
| } |
| |
| type gobKey struct { |
| Kind string |
| StringID string |
| IntID int64 |
| Parent *gobKey |
| AppID string |
| Namespace string |
| } |
| |
| func keyToGobKey(k *Key) *gobKey { |
| if k == nil { |
| return nil |
| } |
| return &gobKey{ |
| Kind: k.kind, |
| StringID: k.stringID, |
| IntID: k.intID, |
| Parent: keyToGobKey(k.parent), |
| AppID: k.appID, |
| Namespace: k.namespace, |
| } |
| } |
| |
| func gobKeyToKey(gk *gobKey) *Key { |
| if gk == nil { |
| return nil |
| } |
| return &Key{ |
| kind: gk.Kind, |
| stringID: gk.StringID, |
| intID: gk.IntID, |
| parent: gobKeyToKey(gk.Parent), |
| appID: gk.AppID, |
| namespace: gk.Namespace, |
| } |
| } |
| |
| func (k *Key) GobEncode() ([]byte, error) { |
| buf := new(bytes.Buffer) |
| if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil { |
| return nil, err |
| } |
| return buf.Bytes(), nil |
| } |
| |
| func (k *Key) GobDecode(buf []byte) error { |
| gk := new(gobKey) |
| if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil { |
| return err |
| } |
| *k = *gobKeyToKey(gk) |
| return nil |
| } |
| |
| func (k *Key) MarshalJSON() ([]byte, error) { |
| return []byte(`"` + k.Encode() + `"`), nil |
| } |
| |
| func (k *Key) UnmarshalJSON(buf []byte) error { |
| if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { |
| return errors.New("datastore: bad JSON key") |
| } |
| k2, err := DecodeKey(string(buf[1 : len(buf)-1])) |
| if err != nil { |
| return err |
| } |
| *k = *k2 |
| return nil |
| } |
| |
| // Encode returns an opaque representation of the key |
| // suitable for use in HTML and URLs. |
| // This is compatible with the Python and Java runtimes. |
| func (k *Key) Encode() string { |
| ref := keyToProto("", k) |
| |
| b, err := proto.Marshal(ref) |
| if err != nil { |
| panic(err) |
| } |
| |
| // Trailing padding is stripped. |
| return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") |
| } |
| |
| // DecodeKey decodes a key from the opaque representation returned by Encode. |
| func DecodeKey(encoded string) (*Key, error) { |
| // Re-add padding. |
| if m := len(encoded) % 4; m != 0 { |
| encoded += strings.Repeat("=", 4-m) |
| } |
| |
| b, err := base64.URLEncoding.DecodeString(encoded) |
| if err != nil { |
| return nil, err |
| } |
| |
| ref := new(pb.Reference) |
| if err := proto.Unmarshal(b, ref); err != nil { |
| return nil, err |
| } |
| |
| return protoToKey(ref) |
| } |
| |
| // NewIncompleteKey creates a new incomplete key. |
| // kind cannot be empty. |
| func NewIncompleteKey(c context.Context, kind string, parent *Key) *Key { |
| return NewKey(c, kind, "", 0, parent) |
| } |
| |
| // NewKey creates a new key. |
| // kind cannot be empty. |
| // Either one or both of stringID and intID must be zero. If both are zero, |
| // the key returned is incomplete. |
| // parent must either be a complete key or nil. |
| func NewKey(c context.Context, kind, stringID string, intID int64, parent *Key) *Key { |
| // If there's a parent key, use its namespace. |
| // Otherwise, use any namespace attached to the context. |
| var namespace string |
| if parent != nil { |
| namespace = parent.namespace |
| } else { |
| namespace = internal.NamespaceFromContext(c) |
| } |
| |
| return &Key{ |
| kind: kind, |
| stringID: stringID, |
| intID: intID, |
| parent: parent, |
| appID: internal.FullyQualifiedAppID(c), |
| namespace: namespace, |
| } |
| } |
| |
| // AllocateIDs returns a range of n integer IDs with the given kind and parent |
| // combination. kind cannot be empty; parent may be nil. The IDs in the range |
| // returned will not be used by the datastore's automatic ID sequence generator |
| // and may be used with NewKey without conflict. |
| // |
| // The range is inclusive at the low end and exclusive at the high end. In |
| // other words, valid intIDs x satisfy low <= x && x < high. |
| // |
| // If no error is returned, low + n == high. |
| func AllocateIDs(c context.Context, kind string, parent *Key, n int) (low, high int64, err error) { |
| if kind == "" { |
| return 0, 0, errors.New("datastore: AllocateIDs given an empty kind") |
| } |
| if n < 0 { |
| return 0, 0, fmt.Errorf("datastore: AllocateIDs given a negative count: %d", n) |
| } |
| if n == 0 { |
| return 0, 0, nil |
| } |
| req := &pb.AllocateIdsRequest{ |
| ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)), |
| Size: proto.Int64(int64(n)), |
| } |
| res := &pb.AllocateIdsResponse{} |
| if err := internal.Call(c, "datastore_v3", "AllocateIds", req, res); err != nil { |
| return 0, 0, err |
| } |
| // The protobuf is inclusive at both ends. Idiomatic Go (e.g. slices, for loops) |
| // is inclusive at the low end and exclusive at the high end, so we add 1. |
| low = res.GetStart() |
| high = res.GetEnd() + 1 |
| if low+int64(n) != high { |
| return 0, 0, fmt.Errorf("datastore: internal error: could not allocate %d IDs", n) |
| } |
| return low, high, nil |
| } |