go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/key.go (about) 1 // Copyright 2015 The LUCI Authors. 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 15 package datastore 16 17 import ( 18 "bytes" 19 "encoding/base64" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "strings" 24 25 "google.golang.org/protobuf/proto" 26 27 pb "go.chromium.org/luci/gae/service/datastore/internal/protos/datastore" 28 ) 29 30 // KeyTok is a single token from a multi-part Key. 31 type KeyTok struct { 32 Kind string 33 IntID int64 34 StringID string 35 } 36 37 // IsIncomplete returns true iff this token doesn't define either a StringID or 38 // an IntID. 39 func (k KeyTok) IsIncomplete() bool { 40 return k.StringID == "" && k.IntID == 0 41 } 42 43 // Special returns true iff this token begins and ends with "__" 44 func (k KeyTok) Special() bool { 45 return len(k.Kind) >= 2 && k.Kind[:2] == "__" && k.Kind[len(k.Kind)-2:] == "__" 46 } 47 48 // ID returns the 'active' id as a Property (either the StringID or the IntID). 49 func (k KeyTok) ID() Property { 50 if k.StringID != "" { 51 return MkProperty(k.StringID) 52 } 53 return MkProperty(k.IntID) 54 } 55 56 // Less returns true iff k would sort before other. 57 func (k KeyTok) Less(other KeyTok) bool { 58 if k.Kind < other.Kind { 59 return true 60 } else if k.Kind > other.Kind { 61 return false 62 } 63 a, b := k.ID(), other.ID() 64 return a.Less(&b) 65 } 66 67 // KeyContext is the context in which a key is generated. 68 type KeyContext struct { 69 AppID string 70 Namespace string 71 } 72 73 // MkKeyContext is a helper function to create a new KeyContext. 74 // 75 // It is preferable to field-based struct initialization because, as a function, 76 // it has the ability to enforce an exact number of parameters. 77 func MkKeyContext(appID, namespace string) KeyContext { 78 return KeyContext{AppID: appID, Namespace: namespace} 79 } 80 81 // Matches returns true iff the AppID and Namespace parameters are the same for 82 // the two KeyContext instances. 83 func (kc KeyContext) Matches(o KeyContext) bool { 84 return (kc.AppID == o.AppID && kc.Namespace == o.Namespace) 85 } 86 87 // NewKeyToks creates a new Key. It is the Key implementation returned from 88 // the various PropertyMap serialization routines, as well as the native key 89 // implementation for the in-memory implementation of gae. 90 // 91 // See NewKeyToks for a version of this function which automatically 92 // provides aid and ns. 93 func (kc KeyContext) NewKeyToks(toks []KeyTok) *Key { 94 if len(toks) == 0 { 95 return nil 96 } 97 newToks := make([]KeyTok, len(toks)) 98 copy(newToks, toks) 99 return &Key{kc, newToks} 100 } 101 102 // NewKey is a wrapper around NewToks which has an interface similar to NewKey 103 // in the SDK. 104 // 105 // See NewKey for a version of this function which automatically provides aid 106 // and ns. 107 func (kc KeyContext) NewKey(kind, stringID string, intID int64, parent *Key) *Key { 108 if parent == nil { 109 return &Key{kc, []KeyTok{{kind, intID, stringID}}} 110 } 111 112 toks := parent.toks 113 newToks := make([]KeyTok, len(toks), len(toks)+1) 114 copy(newToks, toks) 115 newToks = append(newToks, KeyTok{kind, intID, stringID}) 116 return &Key{kc, newToks} 117 } 118 119 // MakeKey is a convenience function for manufacturing a *Key. It should only 120 // be used when elems... is known statically (e.g. in the code) to be correct. 121 // 122 // elems is pairs of (string, string|int|int32|int64) pairs, which correspond to 123 // Kind/id pairs. Example: 124 // 125 // MkKeyContext("aid", "namespace").MakeKey("Parent", 1, "Child", "id") 126 // 127 // Would create the key: 128 // 129 // aid:namespace:/Parent,1/Child,id 130 // 131 // If elems is not parsable (e.g. wrong length, wrong types, etc.) this method 132 // will panic. 133 // 134 // See MakeKey for a version of this function which automatically 135 // provides aid and ns. 136 func (kc KeyContext) MakeKey(elems ...any) *Key { 137 if len(elems) == 0 { 138 return nil 139 } 140 141 if len(elems)%2 != 0 { 142 panic(fmt.Errorf("datastore.MakeKey: odd number of tokens: %v", elems)) 143 } 144 145 toks := make([]KeyTok, len(elems)/2) 146 for i := 0; len(elems) > 0; i, elems = i+1, elems[2:] { 147 knd, ok := elems[0].(string) 148 if !ok { 149 panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elems[i])) 150 } 151 t := &toks[i] 152 t.Kind = knd 153 switch x := elems[1].(type) { 154 case string: 155 t.StringID = x 156 case int: 157 t.IntID = int64(x) 158 case int32: 159 t.IntID = int64(x) 160 case int64: 161 t.IntID = int64(x) 162 case uint16: 163 t.IntID = int64(x) 164 case uint32: 165 t.IntID = int64(x) 166 default: 167 panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x)) 168 } 169 } 170 171 return kc.NewKeyToks(toks) 172 } 173 174 // NewKeyFromMeta constructs a key (potentially partial) based on meta fields. 175 // 176 // Looks at `$key`, `$kind`, `$id`, `$parent`. Returns an error if necessary 177 // fields are missing. 178 func (kc KeyContext) NewKeyFromMeta(mgs MetaGetterSetter) (*Key, error) { 179 if key, _ := GetMetaDefault(mgs, "key", nil).(*Key); key != nil { 180 return key, nil 181 } 182 183 kind := GetMetaDefault(mgs, "kind", "").(string) 184 if kind == "" { 185 return nil, errors.New("unable to extract $kind") 186 } 187 188 // get id - allow both to be default for default keys 189 sid := GetMetaDefault(mgs, "id", "").(string) 190 iid := GetMetaDefault(mgs, "id", 0).(int64) 191 192 par, _ := GetMetaDefault(mgs, "parent", nil).(*Key) 193 194 return kc.NewKey(kind, sid, iid, par), nil 195 } 196 197 // Key is the type used for all datastore operations. 198 type Key struct { 199 kc KeyContext 200 toks []KeyTok 201 } 202 203 var _ interface { 204 json.Marshaler 205 json.Unmarshaler 206 } = (*Key)(nil) 207 208 // NewKeyEncoded decodes and returns a *Key 209 func NewKeyEncoded(encoded string) (ret *Key, err error) { 210 ret = &Key{} 211 // Re-add padding 212 if m := len(encoded) % 4; m != 0 { 213 encoded += strings.Repeat("=", 4-m) 214 } 215 b, err := base64.URLEncoding.DecodeString(encoded) 216 if err != nil { 217 return 218 } 219 220 r := &pb.Reference{} 221 if err = proto.Unmarshal(b, r); err != nil { 222 return 223 } 224 225 ret.kc = MkKeyContext(r.GetApp(), r.GetNameSpace()) 226 ret.toks = make([]KeyTok, len(r.Path.Element)) 227 for i, e := range r.Path.Element { 228 ret.toks[i] = KeyTok{ 229 Kind: e.GetType(), 230 IntID: e.GetId(), 231 StringID: e.GetName(), 232 } 233 } 234 return 235 } 236 237 // LastTok returns the last KeyTok in this Key. Non-nil Keys are always guaranteed 238 // to have at least one token. 239 func (k *Key) LastTok() KeyTok { 240 return k.toks[len(k.toks)-1] 241 } 242 243 // AppID returns the application ID that this Key is for. 244 func (k *Key) AppID() string { return k.kc.AppID } 245 246 // Namespace returns the namespace that this Key is for. 247 func (k *Key) Namespace() string { return k.kc.Namespace } 248 249 // KeyContext returns the KeyContext that this Key is using. 250 func (k *Key) KeyContext() *KeyContext { 251 kc := k.kc 252 return &kc 253 } 254 255 // Kind returns the Kind of the child KeyTok 256 func (k *Key) Kind() string { return k.toks[len(k.toks)-1].Kind } 257 258 // StringID returns the StringID of the child KeyTok 259 func (k *Key) StringID() string { return k.toks[len(k.toks)-1].StringID } 260 261 // IntID returns the IntID of the child KeyTok 262 func (k *Key) IntID() int64 { return k.toks[len(k.toks)-1].IntID } 263 264 // String returns a human-readable representation of the key in the form of 265 // 266 // AID:NS:/Kind,id/Kind,id/... 267 func (k *Key) String() string { 268 b := bytes.NewBuffer(make([]byte, 0, 512)) 269 fmt.Fprintf(b, "%s:%s:", k.kc.AppID, k.kc.Namespace) 270 for _, t := range k.toks { 271 if t.StringID != "" { 272 fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID) 273 } else { 274 fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID) 275 } 276 } 277 return b.String() 278 } 279 280 // IsIncomplete returns true iff the last token of this Key doesn't define 281 // either a StringID or an IntID. 282 func (k *Key) IsIncomplete() bool { 283 return k.LastTok().IsIncomplete() 284 } 285 286 // Valid determines if a key is valid, according to a couple of rules: 287 // - k is not nil 288 // - every token of k: 289 // - (if !allowSpecial) token's kind doesn't start with '__' 290 // - token's kind and appid are non-blank 291 // - token is not incomplete 292 // - all tokens have the same namespace and appid 293 func (k *Key) Valid(allowSpecial bool, kc KeyContext) bool { 294 if !kc.Matches(k.kc) { 295 return false 296 } 297 for _, t := range k.toks { 298 if t.IsIncomplete() { 299 return false 300 } 301 if !allowSpecial && t.Special() { 302 return false 303 } 304 if t.Kind == "" { 305 return false 306 } 307 if t.StringID != "" && t.IntID != 0 { 308 return false 309 } 310 } 311 return true 312 } 313 314 // PartialValid returns true iff this key is suitable for use in a Put 315 // operation. This is the same as Valid(k, false, ...), but also allowing k to 316 // be IsIncomplete(). 317 func (k *Key) PartialValid(kc KeyContext) bool { 318 if k.IsIncomplete() { 319 if !kc.Matches(k.kc) { 320 return false 321 } 322 k = kc.NewKey(k.Kind(), "", 1, k.Parent()) 323 } 324 return k.Valid(false, kc) 325 } 326 327 // Parent returns the parent Key of this *Key, or nil. The parent 328 // will always have the concrete type of *Key. 329 func (k *Key) Parent() *Key { 330 if len(k.toks) <= 1 { 331 return nil 332 } 333 return k.kc.NewKeyToks(k.toks[:len(k.toks)-1]) 334 } 335 336 // MarshalJSON allows this key to be automatically marshaled by encoding/json. 337 func (k *Key) MarshalJSON() ([]byte, error) { 338 return []byte(`"` + k.Encode() + `"`), nil 339 } 340 341 // Encode encodes the provided key as a base64-encoded protobuf. 342 // 343 // This encoding is compatible with the SDK-provided encoding and is agnostic 344 // to the underlying implementation of the Key. 345 // 346 // It's encoded with the urlsafe base64 table without padding. 347 func (k *Key) Encode() string { 348 e := make([]*pb.Path_Element, len(k.toks)) 349 for i, t := range k.toks { 350 t := t 351 e[i] = &pb.Path_Element{ 352 Type: &t.Kind, 353 } 354 if t.StringID != "" { 355 e[i].Name = &t.StringID 356 } else { 357 e[i].Id = &t.IntID 358 } 359 } 360 var namespace *string 361 if ns := k.kc.Namespace; ns != "" { 362 namespace = &ns 363 } 364 r, err := proto.Marshal(&pb.Reference{ 365 App: &k.kc.AppID, 366 NameSpace: namespace, 367 Path: &pb.Path{ 368 Element: e, 369 }, 370 }) 371 if err != nil { 372 panic(err) 373 } 374 375 // trim padding 376 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=") 377 } 378 379 // UnmarshalJSON allows this key to be automatically unmarshaled by encoding/json. 380 func (k *Key) UnmarshalJSON(buf []byte) error { 381 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { 382 return errors.New("datastore: bad JSON key") 383 } 384 nk, err := NewKeyEncoded(string(buf[1 : len(buf)-1])) 385 if err != nil { 386 return err 387 } 388 *k = *nk 389 return nil 390 } 391 392 // GobEncode allows the Key to be encoded in a Gob struct. 393 func (k *Key) GobEncode() ([]byte, error) { 394 return []byte(k.Encode()), nil 395 } 396 397 // GobDecode allows the Key to be decoded in a Gob struct. 398 func (k *Key) GobDecode(buf []byte) error { 399 nk, err := NewKeyEncoded(string(buf)) 400 if err != nil { 401 return err 402 } 403 *k = *nk 404 return nil 405 } 406 407 // Root returns the entity root for the given key. 408 func (k *Key) Root() *Key { 409 if len(k.toks) > 1 { 410 ret := *k 411 ret.toks = ret.toks[:1] 412 return &ret 413 } 414 return k 415 } 416 417 // Less returns true iff k would sort before other. 418 func (k *Key) Less(other *Key) bool { 419 if k.kc.AppID < other.kc.AppID { 420 return true 421 } else if k.kc.AppID > other.kc.AppID { 422 return false 423 } 424 425 if k.kc.Namespace < other.kc.Namespace { 426 return true 427 } else if k.kc.Namespace > other.kc.Namespace { 428 return false 429 } 430 431 lim := len(k.toks) 432 if len(other.toks) < lim { 433 lim = len(other.toks) 434 } 435 for i := 0; i < lim; i++ { 436 a, b := k.toks[i], other.toks[i] 437 if a.Less(b) { 438 return true 439 } else if b.Less(a) { 440 return false 441 } 442 } 443 return len(k.toks) < len(other.toks) 444 } 445 446 // HasAncestor returns true iff other is an ancestor of k (or if other == k). 447 func (k *Key) HasAncestor(other *Key) bool { 448 if !k.kc.Matches(other.kc) { 449 return false 450 } 451 if len(k.toks) < len(other.toks) { 452 return false 453 } 454 for i, tok := range other.toks { 455 if tok != k.toks[i] { 456 return false 457 } 458 } 459 return true 460 } 461 462 // GQL returns a correctly formatted Cloud Datastore GQL key literal. 463 // 464 // The flavor of GQL that this emits is defined here: 465 // 466 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference 467 func (k *Key) GQL() string { 468 ret := &bytes.Buffer{} 469 fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(k.kc.AppID)) 470 if ns := k.kc.Namespace; ns != "" { 471 fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(ns)) 472 } 473 for _, t := range k.toks { 474 if t.IntID != 0 { 475 fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Kind), t.IntID) 476 } else { 477 fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Kind), gqlQuoteString(t.StringID)) 478 } 479 } 480 if _, err := ret.WriteString(")"); err != nil { 481 panic(err) 482 } 483 return ret.String() 484 } 485 486 // Equal returns true iff the two keys represent identical key values. 487 func (k *Key) Equal(other *Key) bool { 488 if k == nil || other == nil { 489 return k == nil && other == nil 490 } 491 return k.IncompleteEqual(other) && (k.LastTok() == other.LastTok()) 492 } 493 494 // IncompleteEqual asserts that, were the two keys incomplete, they would be 495 // equal. 496 // 497 // This asserts equality for the full lineage of the key, except for its last 498 // token ID. 499 func (k *Key) IncompleteEqual(other *Key) (ret bool) { 500 ret = (k.kc.Matches(other.kc) && 501 len(k.toks) == len(other.toks)) 502 if ret { 503 for i, t := range k.toks { 504 if i == len(k.toks)-1 { 505 // Last token: check only Kind. 506 if ret = (t.Kind == other.toks[i].Kind); !ret { 507 return 508 } 509 } else { 510 if ret = t == other.toks[i]; !ret { 511 return 512 } 513 } 514 } 515 } 516 return 517 } 518 519 // Incomplete returns an incomplete version of the key. The ID fields of the 520 // last token will be set to zero/empty. 521 func (k *Key) Incomplete() *Key { 522 if k.IsIncomplete() { 523 return k 524 } 525 return k.kc.NewKey(k.Kind(), "", 0, k.Parent()) 526 } 527 528 // WithID returns the key generated by setting the ID of its last token to 529 // the specified value. 530 // 531 // To generate this, k is reduced to its Incomplete form, then populated with a 532 // new ID. The resulting key will have the same token linage as k (i.e., will 533 // be IncompleteEqual). 534 func (k *Key) WithID(stringID string, intID int64) *Key { 535 if k.StringID() == stringID && k.IntID() == intID { 536 return k 537 } 538 return k.kc.NewKey(k.Kind(), stringID, intID, k.Parent()) 539 } 540 541 // Split componentizes the key into pieces (AppID, Namespace and tokens) 542 // 543 // Each token represents one piece of they key's 'path'. 544 // 545 // toks is guaranteed to be empty if and only if k is nil. If k is non-nil then 546 // it contains at least one token. 547 func (k *Key) Split() (appID, namespace string, toks []KeyTok) { 548 appID = k.kc.AppID 549 namespace = k.kc.Namespace 550 toks = make([]KeyTok, len(k.toks)) 551 copy(toks, k.toks) 552 return 553 } 554 555 // EstimateSize estimates the size of a Key. 556 // 557 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1 558 // as a guide for these values. 559 func (k *Key) EstimateSize() int64 { 560 ret := int64(len(k.kc.AppID)) 561 ret += int64(len(k.kc.Namespace)) 562 for _, t := range k.toks { 563 ret += int64(len(t.Kind)) 564 if t.StringID != "" { 565 ret += int64(len(t.StringID)) 566 } else { 567 ret += 8 568 } 569 } 570 return ret 571 }