| // Copyright 2014 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package datastore | |
| import ( | |
| "bytes" | |
| "context" | |
| "encoding/base64" | |
| "encoding/gob" | |
| "errors" | |
| "strconv" | |
| "strings" | |
| "github.com/golang/protobuf/proto" | |
| pb "google.golang.org/genproto/googleapis/datastore/v1" | |
| ) | |
| // Key represents the datastore key for a stored entity. | |
| type Key struct { | |
| // Kind cannot be empty. | |
| Kind string | |
| // Either ID or Name must be zero for the Key to be valid. | |
| // If both are zero, the Key is incomplete. | |
| ID int64 | |
| Name string | |
| // Parent must either be a complete Key or nil. | |
| Parent *Key | |
| // Namespace provides the ability to partition your data for multiple | |
| // tenants. In most cases, it is not necessary to specify a namespace. | |
| // See docs on datastore multitenancy for details: | |
| // https://cloud.google.com/datastore/docs/concepts/multitenancy | |
| Namespace string | |
| } | |
| // Incomplete reports whether the key does not refer to a stored entity. | |
| func (k *Key) Incomplete() bool { | |
| return k.Name == "" && k.ID == 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 == "" { | |
| return false | |
| } | |
| if k.Name != "" && k.ID != 0 { | |
| return false | |
| } | |
| if k.Parent != nil { | |
| if k.Parent.Incomplete() { | |
| return false | |
| } | |
| if k.Parent.Namespace != k.Namespace { | |
| return false | |
| } | |
| } | |
| } | |
| return true | |
| } | |
| // Equal reports whether two keys are equal. Two keys are equal if they are | |
| // both nil, or if their kinds, IDs, names, namespaces and parents are equal. | |
| func (k *Key) Equal(o *Key) bool { | |
| for { | |
| if k == nil || o == nil { | |
| return k == o // if either is nil, both must be nil | |
| } | |
| if k.Namespace != o.Namespace || k.Name != o.Name || k.ID != o.ID || k.Kind != o.Kind { | |
| return false | |
| } | |
| if k.Parent == nil && o.Parent == nil { | |
| return true | |
| } | |
| k = k.Parent | |
| o = o.Parent | |
| } | |
| } | |
| // 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.Name != "" { | |
| b.WriteString(k.Name) | |
| } else { | |
| b.WriteString(strconv.FormatInt(k.ID, 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() | |
| } | |
| // Note: Fields not renamed compared to appengine gobKey struct | |
| // This ensures gobs created by appengine can be read here, and vice/versa | |
| 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.Name, | |
| IntID: k.ID, | |
| Parent: keyToGobKey(k.Parent), | |
| Namespace: k.Namespace, | |
| } | |
| } | |
| func gobKeyToKey(gk *gobKey) *Key { | |
| if gk == nil { | |
| return nil | |
| } | |
| return &Key{ | |
| Kind: gk.Kind, | |
| Name: gk.StringID, | |
| ID: gk.IntID, | |
| Parent: gobKeyToKey(gk.Parent), | |
| Namespace: gk.Namespace, | |
| } | |
| } | |
| // GobEncode marshals the key into a sequence of bytes | |
| // using an encoding/gob.Encoder. | |
| 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 | |
| } | |
| // GobDecode unmarshals a sequence of bytes using an encoding/gob.Decoder. | |
| 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 | |
| } | |
| // MarshalJSON marshals the key into JSON. | |
| func (k *Key) MarshalJSON() ([]byte, error) { | |
| return []byte(`"` + k.Encode() + `"`), nil | |
| } | |
| // UnmarshalJSON unmarshals a key JSON object into a Key. | |
| 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 { | |
| pKey := keyToProto(k) | |
| b, err := proto.Marshal(pKey) | |
| 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) { | |
| k, err := decodeCloudKey(encoded) | |
| if err != nil { | |
| // Couldn't decode it as a Cloud Datastore key, try decoding it as a key encoded by google.golang.org/appengine/datastore. | |
| if k := decodeGAEKey(encoded); k != nil { | |
| return k, nil | |
| } | |
| // Return original error. | |
| return nil, err | |
| } | |
| return k, nil | |
| } | |
| func decodeCloudKey(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 | |
| } | |
| pKey := new(pb.Key) | |
| if err := proto.Unmarshal(b, pKey); err != nil { | |
| return nil, err | |
| } | |
| return protoToKey(pKey) | |
| } | |
| // AllocateIDs accepts a slice of incomplete keys and returns a | |
| // slice of complete keys that are guaranteed to be valid in the datastore. | |
| func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) { | |
| if keys == nil { | |
| return nil, nil | |
| } | |
| req := &pb.AllocateIdsRequest{ | |
| ProjectId: c.dataset, | |
| Keys: multiKeyToProto(keys), | |
| } | |
| resp, err := c.client.AllocateIds(ctx, req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return multiProtoToKey(resp.Keys) | |
| } | |
| // IncompleteKey creates a new incomplete key. | |
| // The supplied kind cannot be empty. | |
| // The namespace of the new key is empty. | |
| func IncompleteKey(kind string, parent *Key) *Key { | |
| return &Key{ | |
| Kind: kind, | |
| Parent: parent, | |
| } | |
| } | |
| // NameKey creates a new key with a name. | |
| // The supplied kind cannot be empty. | |
| // The supplied parent must either be a complete key or nil. | |
| // The namespace of the new key is empty. | |
| func NameKey(kind, name string, parent *Key) *Key { | |
| return &Key{ | |
| Kind: kind, | |
| Name: name, | |
| Parent: parent, | |
| } | |
| } | |
| // IDKey creates a new key with an ID. | |
| // The supplied kind cannot be empty. | |
| // The supplied parent must either be a complete key or nil. | |
| // The namespace of the new key is empty. | |
| func IDKey(kind string, id int64, parent *Key) *Key { | |
| return &Key{ | |
| Kind: kind, | |
| ID: id, | |
| Parent: parent, | |
| } | |
| } |