1// Copyright 2014 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package datastore 16 17import ( 18 "bytes" 19 "context" 20 "encoding/base64" 21 "encoding/gob" 22 "errors" 23 "strconv" 24 "strings" 25 26 "github.com/golang/protobuf/proto" 27 pb "google.golang.org/genproto/googleapis/datastore/v1" 28) 29 30// Key represents the datastore key for a stored entity. 31type Key struct { 32 // Kind cannot be empty. 33 Kind string 34 // Either ID or Name must be zero for the Key to be valid. 35 // If both are zero, the Key is incomplete. 36 ID int64 37 Name string 38 // Parent must either be a complete Key or nil. 39 Parent *Key 40 41 // Namespace provides the ability to partition your data for multiple 42 // tenants. In most cases, it is not necessary to specify a namespace. 43 // See docs on datastore multitenancy for details: 44 // https://cloud.google.com/datastore/docs/concepts/multitenancy 45 Namespace string 46} 47 48// Incomplete reports whether the key does not refer to a stored entity. 49func (k *Key) Incomplete() bool { 50 return k.Name == "" && k.ID == 0 51} 52 53// valid returns whether the key is valid. 54func (k *Key) valid() bool { 55 if k == nil { 56 return false 57 } 58 for ; k != nil; k = k.Parent { 59 if k.Kind == "" { 60 return false 61 } 62 if k.Name != "" && k.ID != 0 { 63 return false 64 } 65 if k.Parent != nil { 66 if k.Parent.Incomplete() { 67 return false 68 } 69 if k.Parent.Namespace != k.Namespace { 70 return false 71 } 72 } 73 } 74 return true 75} 76 77// Equal reports whether two keys are equal. Two keys are equal if they are 78// both nil, or if their kinds, IDs, names, namespaces and parents are equal. 79func (k *Key) Equal(o *Key) bool { 80 for { 81 if k == nil || o == nil { 82 return k == o // if either is nil, both must be nil 83 } 84 if k.Namespace != o.Namespace || k.Name != o.Name || k.ID != o.ID || k.Kind != o.Kind { 85 return false 86 } 87 if k.Parent == nil && o.Parent == nil { 88 return true 89 } 90 k = k.Parent 91 o = o.Parent 92 } 93} 94 95// marshal marshals the key's string representation to the buffer. 96func (k *Key) marshal(b *bytes.Buffer) { 97 if k.Parent != nil { 98 k.Parent.marshal(b) 99 } 100 b.WriteByte('/') 101 b.WriteString(k.Kind) 102 b.WriteByte(',') 103 if k.Name != "" { 104 b.WriteString(k.Name) 105 } else { 106 b.WriteString(strconv.FormatInt(k.ID, 10)) 107 } 108} 109 110// String returns a string representation of the key. 111func (k *Key) String() string { 112 if k == nil { 113 return "" 114 } 115 b := bytes.NewBuffer(make([]byte, 0, 512)) 116 k.marshal(b) 117 return b.String() 118} 119 120// Note: Fields not renamed compared to appengine gobKey struct 121// This ensures gobs created by appengine can be read here, and vice/versa 122type gobKey struct { 123 Kind string 124 StringID string 125 IntID int64 126 Parent *gobKey 127 AppID string 128 Namespace string 129} 130 131func keyToGobKey(k *Key) *gobKey { 132 if k == nil { 133 return nil 134 } 135 return &gobKey{ 136 Kind: k.Kind, 137 StringID: k.Name, 138 IntID: k.ID, 139 Parent: keyToGobKey(k.Parent), 140 Namespace: k.Namespace, 141 } 142} 143 144func gobKeyToKey(gk *gobKey) *Key { 145 if gk == nil { 146 return nil 147 } 148 return &Key{ 149 Kind: gk.Kind, 150 Name: gk.StringID, 151 ID: gk.IntID, 152 Parent: gobKeyToKey(gk.Parent), 153 Namespace: gk.Namespace, 154 } 155} 156 157// GobEncode marshals the key into a sequence of bytes 158// using an encoding/gob.Encoder. 159func (k *Key) GobEncode() ([]byte, error) { 160 buf := new(bytes.Buffer) 161 if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil { 162 return nil, err 163 } 164 return buf.Bytes(), nil 165} 166 167// GobDecode unmarshals a sequence of bytes using an encoding/gob.Decoder. 168func (k *Key) GobDecode(buf []byte) error { 169 gk := new(gobKey) 170 if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil { 171 return err 172 } 173 *k = *gobKeyToKey(gk) 174 return nil 175} 176 177// MarshalJSON marshals the key into JSON. 178func (k *Key) MarshalJSON() ([]byte, error) { 179 return []byte(`"` + k.Encode() + `"`), nil 180} 181 182// UnmarshalJSON unmarshals a key JSON object into a Key. 183func (k *Key) UnmarshalJSON(buf []byte) error { 184 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { 185 return errors.New("datastore: bad JSON key") 186 } 187 k2, err := DecodeKey(string(buf[1 : len(buf)-1])) 188 if err != nil { 189 return err 190 } 191 *k = *k2 192 return nil 193} 194 195// Encode returns an opaque representation of the key 196// suitable for use in HTML and URLs. 197// This is compatible with the Python and Java runtimes. 198func (k *Key) Encode() string { 199 pKey := keyToProto(k) 200 201 b, err := proto.Marshal(pKey) 202 if err != nil { 203 panic(err) 204 } 205 206 // Trailing padding is stripped. 207 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") 208} 209 210// DecodeKey decodes a key from the opaque representation returned by Encode. 211func DecodeKey(encoded string) (*Key, error) { 212 k, err := decodeCloudKey(encoded) 213 if err != nil { 214 // Couldn't decode it as a Cloud Datastore key, try decoding it as a key encoded by google.golang.org/appengine/datastore. 215 if k := decodeGAEKey(encoded); k != nil { 216 return k, nil 217 } 218 // Return original error. 219 return nil, err 220 } 221 return k, nil 222} 223 224func decodeCloudKey(encoded string) (*Key, error) { 225 // Re-add padding. 226 if m := len(encoded) % 4; m != 0 { 227 encoded += strings.Repeat("=", 4-m) 228 } 229 230 b, err := base64.URLEncoding.DecodeString(encoded) 231 if err != nil { 232 return nil, err 233 } 234 235 pKey := new(pb.Key) 236 if err := proto.Unmarshal(b, pKey); err != nil { 237 return nil, err 238 } 239 return protoToKey(pKey) 240} 241 242// AllocateIDs accepts a slice of incomplete keys and returns a 243// slice of complete keys that are guaranteed to be valid in the datastore. 244func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) { 245 if keys == nil { 246 return nil, nil 247 } 248 249 req := &pb.AllocateIdsRequest{ 250 ProjectId: c.dataset, 251 Keys: multiKeyToProto(keys), 252 } 253 resp, err := c.client.AllocateIds(ctx, req) 254 if err != nil { 255 return nil, err 256 } 257 258 return multiProtoToKey(resp.Keys) 259} 260 261// IncompleteKey creates a new incomplete key. 262// The supplied kind cannot be empty. 263// The namespace of the new key is empty. 264func IncompleteKey(kind string, parent *Key) *Key { 265 return &Key{ 266 Kind: kind, 267 Parent: parent, 268 } 269} 270 271// NameKey creates a new key with a name. 272// The supplied kind cannot be empty. 273// The supplied parent must either be a complete key or nil. 274// The namespace of the new key is empty. 275func NameKey(kind, name string, parent *Key) *Key { 276 return &Key{ 277 Kind: kind, 278 Name: name, 279 Parent: parent, 280 } 281} 282 283// IDKey creates a new key with an ID. 284// The supplied kind cannot be empty. 285// The supplied parent must either be a complete key or nil. 286// The namespace of the new key is empty. 287func IDKey(kind string, id int64, parent *Key) *Key { 288 return &Key{ 289 Kind: kind, 290 ID: id, 291 Parent: parent, 292 } 293} 294