OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package datastore |
| 6 |
| 7 import ( |
| 8 "bytes" |
| 9 "encoding/base64" |
| 10 "encoding/json" |
| 11 "errors" |
| 12 "fmt" |
| 13 "strings" |
| 14 |
| 15 "github.com/golang/protobuf/proto" |
| 16 pb "github.com/luci/gae/service/datastore/internal/protos/datastore" |
| 17 ) |
| 18 |
| 19 // KeyTok is a single token from a multi-part Key. |
| 20 type KeyTok struct { |
| 21 Kind string |
| 22 IntID int64 |
| 23 StringID string |
| 24 } |
| 25 |
| 26 // Incomplete returns true iff this token doesn't define either a StringID or |
| 27 // an IntID. |
| 28 func (k KeyTok) Incomplete() bool { |
| 29 return k.StringID == "" && k.IntID == 0 |
| 30 } |
| 31 |
| 32 // Special returns true iff this token begins and ends with "__" |
| 33 func (k KeyTok) Special() bool { |
| 34 return len(k.Kind) >= 2 && k.Kind[:2] == "__" && k.Kind[len(k.Kind)-2:]
== "__" |
| 35 } |
| 36 |
| 37 // ID returns the 'active' id as a Property (either the StringID or the IntID). |
| 38 func (k KeyTok) ID() Property { |
| 39 if k.StringID != "" { |
| 40 return Property{value: k.StringID, propType: PTString} |
| 41 } |
| 42 return Property{value: k.IntID, propType: PTInt} |
| 43 } |
| 44 |
| 45 // Less returns true iff k would sort before other. |
| 46 func (k KeyTok) Less(other KeyTok) bool { |
| 47 if k.Kind < other.Kind { |
| 48 return true |
| 49 } else if k.Kind > other.Kind { |
| 50 return false |
| 51 } |
| 52 a, b := k.ID(), other.ID() |
| 53 return a.Less(&b) |
| 54 } |
| 55 |
| 56 // Key is the type used for all datastore operations. |
| 57 type Key struct { |
| 58 appID string |
| 59 namespace string |
| 60 toks []KeyTok |
| 61 } |
| 62 |
| 63 var _ interface { |
| 64 json.Marshaler |
| 65 json.Unmarshaler |
| 66 } = (*Key)(nil) |
| 67 |
| 68 // NewKeyToks creates a new Key. It is the Key implementation returned from the |
| 69 // various PropertyMap serialization routines, as well as the native key |
| 70 // implementation for the in-memory implementation of gae. |
| 71 // |
| 72 // See Interface.NewKeyToks for a version of this function which automatically |
| 73 // provides aid and ns. |
| 74 func NewKeyToks(aid, ns string, toks []KeyTok) *Key { |
| 75 if len(toks) == 0 { |
| 76 return nil |
| 77 } |
| 78 newToks := make([]KeyTok, len(toks)) |
| 79 copy(newToks, toks) |
| 80 return &Key{aid, ns, newToks} |
| 81 } |
| 82 |
| 83 // NewKey is a wrapper around NewToks which has an interface similar |
| 84 // to NewKey in the SDK. |
| 85 // |
| 86 // See Interface.NewKey for a version of this function which automatically |
| 87 // provides aid and ns. |
| 88 func NewKey(aid, ns, kind, stringID string, intID int64, parent *Key) *Key { |
| 89 if parent == nil { |
| 90 return &Key{aid, ns, []KeyTok{{kind, intID, stringID}}} |
| 91 } |
| 92 |
| 93 toks := parent.toks |
| 94 newToks := make([]KeyTok, len(toks), len(toks)+1) |
| 95 copy(newToks, toks) |
| 96 newToks = append(newToks, KeyTok{kind, intID, stringID}) |
| 97 return &Key{aid, ns, newToks} |
| 98 } |
| 99 |
| 100 // MakeKey is a convenience function for manufacturing a *Key. It should only |
| 101 // be used when elems... is known statically (e.g. in the code) to be correct. |
| 102 // |
| 103 // elems is pairs of (string, string|int|int32|int64) pairs, which correspond to |
| 104 // Kind/id pairs. Example: |
| 105 // MakeKey("aid", "namespace", "Parent", 1, "Child", "id") |
| 106 // |
| 107 // Would create the key: |
| 108 // aid:namespace:/Parent,1/Child,id |
| 109 // |
| 110 // If elems is not parsable (e.g. wrong length, wrong types, etc.) this method |
| 111 // will panic. |
| 112 // |
| 113 // See Interface.MakeKey for a version of this function which automatically |
| 114 // provides aid and ns. |
| 115 func MakeKey(aid, ns string, elems ...interface{}) *Key { |
| 116 if len(elems) == 0 { |
| 117 return nil |
| 118 } |
| 119 |
| 120 if len(elems)%2 != 0 { |
| 121 panic(fmt.Errorf("datastore.MakeKey: odd number of tokens: %v",
elems)) |
| 122 } |
| 123 |
| 124 toks := make([]KeyTok, len(elems)/2) |
| 125 for i := 0; len(elems) > 0; i, elems = i+1, elems[2:] { |
| 126 knd, ok := elems[0].(string) |
| 127 if !ok { |
| 128 panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elem
s[i])) |
| 129 } |
| 130 t := &toks[i] |
| 131 t.Kind = knd |
| 132 switch x := elems[1].(type) { |
| 133 case string: |
| 134 t.StringID = x |
| 135 case int: |
| 136 t.IntID = int64(x) |
| 137 case int32: |
| 138 t.IntID = int64(x) |
| 139 case int64: |
| 140 t.IntID = int64(x) |
| 141 default: |
| 142 panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x)) |
| 143 } |
| 144 } |
| 145 |
| 146 return NewKeyToks(aid, ns, toks) |
| 147 } |
| 148 |
| 149 // NewKeyEncoded decodes and returns a *Key |
| 150 func NewKeyEncoded(encoded string) (ret *Key, err error) { |
| 151 ret = &Key{} |
| 152 // Re-add padding |
| 153 if m := len(encoded) % 4; m != 0 { |
| 154 encoded += strings.Repeat("=", 4-m) |
| 155 } |
| 156 b, err := base64.URLEncoding.DecodeString(encoded) |
| 157 if err != nil { |
| 158 return |
| 159 } |
| 160 |
| 161 r := &pb.Reference{} |
| 162 if err = proto.Unmarshal(b, r); err != nil { |
| 163 return |
| 164 } |
| 165 |
| 166 ret.appID = r.GetApp() |
| 167 ret.namespace = r.GetNameSpace() |
| 168 ret.toks = make([]KeyTok, len(r.Path.Element)) |
| 169 for i, e := range r.Path.Element { |
| 170 ret.toks[i] = KeyTok{ |
| 171 Kind: e.GetType(), |
| 172 IntID: e.GetId(), |
| 173 StringID: e.GetName(), |
| 174 } |
| 175 } |
| 176 return |
| 177 } |
| 178 |
| 179 // Last returns the last KeyTok in this Key. Non-nil Keys are always guaranteed |
| 180 // to have at least one token. |
| 181 func (k *Key) Last() KeyTok { |
| 182 return k.toks[len(k.toks)-1] |
| 183 } |
| 184 |
| 185 // AppID returns the application ID that this Key is for. |
| 186 func (k *Key) AppID() string { return k.appID } |
| 187 |
| 188 // Namespace returns the namespace that this Key is for. |
| 189 func (k *Key) Namespace() string { return k.namespace } |
| 190 |
| 191 // String returns a human-readable representation of the key in the form of |
| 192 // AID:NS:/Kind,id/Kind,id/... |
| 193 func (k *Key) String() string { |
| 194 b := bytes.NewBuffer(make([]byte, 0, 512)) |
| 195 fmt.Fprintf(b, "%s:%s:", k.appID, k.namespace) |
| 196 for _, t := range k.toks { |
| 197 if t.StringID != "" { |
| 198 fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID) |
| 199 } else { |
| 200 fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID) |
| 201 } |
| 202 } |
| 203 return b.String() |
| 204 } |
| 205 |
| 206 // Incomplete returns true iff k doesn't have an id yet. |
| 207 func (k *Key) Incomplete() bool { |
| 208 return k.Last().Incomplete() |
| 209 } |
| 210 |
| 211 // Valid determines if a key is valid, according to a couple rules: |
| 212 // - k is not nil |
| 213 // - every token of k: |
| 214 // - (if !allowSpecial) token's kind doesn't start with '__' |
| 215 // - token's kind and appid are non-blank |
| 216 // - token is not incomplete |
| 217 // - all tokens have the same namespace and appid |
| 218 func (k *Key) Valid(allowSpecial bool, aid, ns string) bool { |
| 219 if aid != k.appID || ns != k.namespace { |
| 220 return false |
| 221 } |
| 222 for _, t := range k.toks { |
| 223 if t.Incomplete() { |
| 224 return false |
| 225 } |
| 226 if !allowSpecial && t.Special() { |
| 227 return false |
| 228 } |
| 229 if t.Kind == "" { |
| 230 return false |
| 231 } |
| 232 if t.StringID != "" && t.IntID != 0 { |
| 233 return false |
| 234 } |
| 235 } |
| 236 return true |
| 237 } |
| 238 |
| 239 // PartialValid returns true iff this key is suitable for use in a Put |
| 240 // operation. This is the same as Valid(k, false, ...), but also allowing k to |
| 241 // be Incomplete(). |
| 242 func (k *Key) PartialValid(aid, ns string) bool { |
| 243 if k.Incomplete() { |
| 244 k = NewKey(k.AppID(), k.Namespace(), k.Last().Kind, "", 1, k.Par
ent()) |
| 245 } |
| 246 return k.Valid(false, aid, ns) |
| 247 } |
| 248 |
| 249 // Parent returns the parent Key of this *Key, or nil. The parent |
| 250 // will always have the concrete type of *Key. |
| 251 func (k *Key) Parent() *Key { |
| 252 if len(k.toks) <= 1 { |
| 253 return nil |
| 254 } |
| 255 return &Key{k.appID, k.namespace, k.toks[:len(k.toks)-1]} |
| 256 } |
| 257 |
| 258 // MarshalJSON allows this key to be automatically marshaled by encoding/json. |
| 259 func (k *Key) MarshalJSON() ([]byte, error) { |
| 260 return []byte(`"` + k.Encode() + `"`), nil |
| 261 } |
| 262 |
| 263 // Encode encodes the provided key as a base64-encoded protobuf. |
| 264 // |
| 265 // This encoding is compatible with the SDK-provided encoding and is agnostic |
| 266 // to the underlying implementation of the Key. |
| 267 // |
| 268 // It's encoded with the urlsafe base64 table without padding. |
| 269 func (k *Key) Encode() string { |
| 270 e := make([]*pb.Path_Element, len(k.toks)) |
| 271 for i, t := range k.toks { |
| 272 t := t |
| 273 e[i] = &pb.Path_Element{ |
| 274 Type: &t.Kind, |
| 275 } |
| 276 if t.StringID != "" { |
| 277 e[i].Name = &t.StringID |
| 278 } else { |
| 279 e[i].Id = &t.IntID |
| 280 } |
| 281 } |
| 282 var namespace *string |
| 283 if k.namespace != "" { |
| 284 namespace = &k.namespace |
| 285 } |
| 286 r, err := proto.Marshal(&pb.Reference{ |
| 287 App: &k.appID, |
| 288 NameSpace: namespace, |
| 289 Path: &pb.Path{ |
| 290 Element: e, |
| 291 }, |
| 292 }) |
| 293 if err != nil { |
| 294 panic(err) |
| 295 } |
| 296 |
| 297 // trim padding |
| 298 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=") |
| 299 } |
| 300 |
| 301 // UnmarshalJSON allows this key to be automatically unmarshaled by encoding/jso
n. |
| 302 func (k *Key) UnmarshalJSON(buf []byte) error { |
| 303 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { |
| 304 return errors.New("datastore: bad JSON key") |
| 305 } |
| 306 nk, err := NewKeyEncoded(string(buf[1 : len(buf)-1])) |
| 307 if err != nil { |
| 308 return err |
| 309 } |
| 310 *k = *nk |
| 311 return nil |
| 312 } |
| 313 |
| 314 // Root returns the entity root for the given key. |
| 315 func (k *Key) Root() *Key { |
| 316 if len(k.toks) > 1 { |
| 317 ret := *k |
| 318 ret.toks = ret.toks[:1] |
| 319 return &ret |
| 320 } |
| 321 return k |
| 322 } |
| 323 |
| 324 // Less returns true iff k would sort before other. |
| 325 func (k *Key) Less(other *Key) bool { |
| 326 if k.appID < other.appID { |
| 327 return true |
| 328 } else if k.appID > other.appID { |
| 329 return false |
| 330 } |
| 331 |
| 332 if k.namespace < other.namespace { |
| 333 return true |
| 334 } else if k.namespace > other.namespace { |
| 335 return false |
| 336 } |
| 337 |
| 338 lim := len(k.toks) |
| 339 if len(other.toks) < lim { |
| 340 lim = len(other.toks) |
| 341 } |
| 342 for i := 0; i < lim; i++ { |
| 343 a, b := k.toks[i], other.toks[i] |
| 344 if a.Less(b) { |
| 345 return true |
| 346 } else if b.Less(a) { |
| 347 return false |
| 348 } |
| 349 } |
| 350 return len(k.toks) < len(other.toks) |
| 351 } |
| 352 |
| 353 // GQL returns a correctly formatted Cloud Datastore GQL key literal. |
| 354 // |
| 355 // The flavor of GQL that this emits is defined here: |
| 356 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference |
| 357 func (k *Key) GQL() string { |
| 358 ret := &bytes.Buffer{} |
| 359 fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(k.appID)) |
| 360 if k.namespace != "" { |
| 361 fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(k.namespace)) |
| 362 } |
| 363 for _, t := range k.toks { |
| 364 if t.IntID != 0 { |
| 365 fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Kind), t.I
ntID) |
| 366 } else { |
| 367 fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Kind), gql
QuoteString(t.StringID)) |
| 368 } |
| 369 } |
| 370 if _, err := ret.WriteString(")"); err != nil { |
| 371 panic(err) |
| 372 } |
| 373 return ret.String() |
| 374 } |
| 375 |
| 376 // Equal returns true iff the two keys represent identical key values. |
| 377 func (k *Key) Equal(other *Key) (ret bool) { |
| 378 ret = (k.appID == other.appID && |
| 379 k.namespace == other.namespace && |
| 380 len(k.toks) == len(other.toks)) |
| 381 if ret { |
| 382 for i, t := range k.toks { |
| 383 if ret = t == other.toks[i]; !ret { |
| 384 return |
| 385 } |
| 386 } |
| 387 } |
| 388 return |
| 389 } |
| 390 |
| 391 // Split componentizes the key into pieces (AppID, Namespace and tokens) |
| 392 // |
| 393 // Each token represents one piece of they key's 'path'. |
| 394 // |
| 395 // toks is guaranteed to be empty if and only if k is nil. If k is non-nil then |
| 396 // it contains at least one token. |
| 397 func (k *Key) Split() (appID, namespace string, toks []KeyTok) { |
| 398 appID = k.appID |
| 399 namespace = k.namespace |
| 400 toks = make([]KeyTok, len(k.toks)) |
| 401 copy(toks, k.toks) |
| 402 return |
| 403 } |
OLD | NEW |