go.mercari.io/datastore@v1.8.2/boom/boom.go (about) 1 package boom 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 9 "go.mercari.io/datastore" 10 ) 11 12 var typeOfKey = reflect.TypeOf((*datastore.Key)(nil)).Elem() 13 14 // Boom is a datastore client wrapper to make it easy to understand the handling of Key. 15 type Boom struct { 16 Context context.Context 17 Client datastore.Client 18 } 19 20 func (bm *Boom) extractKeys(src interface{}) ([]datastore.Key, error) { 21 v := reflect.Indirect(reflect.ValueOf(src)) 22 if v.Kind() != reflect.Slice { 23 return nil, fmt.Errorf("boom: value must be a slice or pointer-to-slice or key-slice") 24 } 25 l := v.Len() 26 27 keys := make([]datastore.Key, 0, l) 28 for i := 0; i < l; i++ { 29 v := v.Index(i) 30 obj := v.Interface() 31 32 key, ok := obj.(datastore.Key) 33 if ok { 34 keys = append(keys, key) 35 continue 36 } 37 38 key, err := bm.KeyError(obj) 39 if err != nil { 40 return nil, err 41 } 42 keys = append(keys, key) 43 } 44 return keys, nil 45 } 46 47 func (bm *Boom) setStructKey(src interface{}, key datastore.Key) error { 48 v := reflect.ValueOf(src) 49 t := v.Type() 50 k := t.Kind() 51 52 if k != reflect.Ptr { 53 return fmt.Errorf("boom: Expected pointer to struct, got instead: %v", k) 54 } 55 56 v = reflect.Indirect(v) 57 t = v.Type() 58 k = t.Kind() 59 60 if k != reflect.Struct { 61 return fmt.Errorf(fmt.Sprintf("boom: Expected struct, got instead: %v", k)) 62 } 63 64 idSet := false 65 kindSet := false 66 parentSet := false 67 for i := 0; i < v.NumField(); i++ { 68 tf := t.Field(i) 69 vf := v.Field(i) 70 71 if !vf.CanSet() { 72 continue 73 } 74 75 tag := tf.Tag.Get("boom") 76 if tag == "" { 77 tag = tf.Tag.Get("goon") 78 } 79 tagValues := strings.SplitN(tag, ",", 2) 80 if len(tagValues) == 0 { 81 continue 82 } 83 84 switch tagValues[0] { 85 case "id": 86 if idSet { 87 return fmt.Errorf("boom: Only one field may be marked id") 88 } 89 90 pt, ok := vf.Interface().(datastore.PropertyTranslator) 91 if ok { 92 pv, err := pt.FromPropertyValue(bm.Context, datastore.Property{Value: key}) 93 if err != nil { 94 return err 95 } 96 97 vf.Set(reflect.ValueOf(pv)) 98 99 } else { 100 switch vf.Kind() { 101 case reflect.Int64: 102 vf.SetInt(key.ID()) 103 case reflect.String: 104 vf.SetString(key.Name()) 105 } 106 } 107 108 idSet = true 109 110 case "kind": 111 if kindSet { 112 return fmt.Errorf("boom: Only one field may be marked kind") 113 } 114 if vf.Kind() == reflect.String { 115 if (len(tagValues) <= 1 || key.Kind() != tagValues[1]) && t.Name() != key.Kind() { 116 vf.Set(reflect.ValueOf(key.Kind())) 117 } 118 kindSet = true 119 } 120 121 case "parent": 122 if parentSet { 123 return fmt.Errorf("boom: Only one field may be marked parent") 124 } 125 126 pt, ok := vf.Interface().(datastore.PropertyTranslator) 127 if ok { 128 pv, err := pt.FromPropertyValue(bm.Context, datastore.Property{Value: key.ParentKey()}) 129 if err != nil { 130 return err 131 } 132 133 vf.Set(reflect.ValueOf(pv)) 134 parentSet = true 135 136 } else { 137 vfType := vf.Type() 138 if vfType.ConvertibleTo(typeOfKey) { 139 if key.ParentKey() != nil { 140 vf.Set(reflect.ValueOf(key.ParentKey()).Convert(vfType)) 141 } 142 parentSet = true 143 } 144 } 145 } 146 } 147 148 if !idSet { 149 return fmt.Errorf("boom: Could not set id field") 150 } 151 152 return nil 153 } 154 155 // Kind retrieves kind name from struct. 156 func (bm *Boom) Kind(src interface{}) string { 157 // bm.KeyError を使うと id が PropertyTranslator だった場合に無限再起する場合がある 158 kind, err := bm.kindErr(src) 159 if err != nil { 160 return "" 161 } 162 return kind 163 } 164 165 func (bm *Boom) kindErr(src interface{}) (string, error) { 166 v := reflect.Indirect(reflect.ValueOf(src)) 167 t := v.Type() 168 k := t.Kind() 169 170 if k != reflect.Struct { 171 return "", fmt.Errorf("boom: Expected struct, got instead: %v", k) 172 } 173 174 var kind string 175 176 for i := 0; i < v.NumField(); i++ { 177 tf := t.Field(i) 178 vf := v.Field(i) 179 180 tag := tf.Tag.Get("boom") 181 if tag == "" { 182 tag = tf.Tag.Get("goon") 183 } 184 tagValues := strings.SplitN(tag, ",", 2) 185 if len(tagValues) > 0 { 186 switch tagValues[0] { 187 case "kind": 188 if vf.Kind() == reflect.String { 189 if kind != "" { 190 return "", fmt.Errorf("boom: Only one field may be marked kind") 191 } 192 kind = vf.String() 193 if kind == "" && len(tagValues) > 1 && tagValues[1] != "" { 194 kind = tagValues[1] 195 } 196 } 197 } 198 } 199 } 200 201 if kind == "" { 202 kind = t.Name() 203 } 204 205 return kind, nil 206 } 207 208 // Key retrieves datastore key from struct without error occurred. 209 func (bm *Boom) Key(src interface{}) datastore.Key { 210 key, err := bm.KeyError(src) 211 if err != nil { 212 return nil 213 } 214 215 return key 216 } 217 218 // KeyError retrieves datastore key from struct with error occurred. 219 func (bm *Boom) KeyError(src interface{}) (datastore.Key, error) { 220 v := reflect.Indirect(reflect.ValueOf(src)) 221 t := v.Type() 222 k := t.Kind() 223 224 if k != reflect.Struct { 225 return nil, fmt.Errorf("boom: Expected struct, got instead: %v", k) 226 } 227 228 var parent datastore.Key 229 var key datastore.Key 230 var keyName string 231 var keyID int64 232 var kind string 233 234 for i := 0; i < v.NumField(); i++ { 235 tf := t.Field(i) 236 vf := v.Field(i) 237 238 tag := tf.Tag.Get("boom") 239 if tag == "" { 240 tag = tf.Tag.Get("goon") 241 } 242 tagValues := strings.SplitN(tag, ",", 2) 243 if len(tagValues) > 0 { 244 switch tagValues[0] { 245 case "id": 246 247 pt, ok := vf.Interface().(datastore.PropertyTranslator) 248 if ok { 249 pv, err := pt.ToPropertyValue(bm.Context) 250 if err != nil { 251 return nil, err 252 } 253 if id, ok := pv.(int64); ok { 254 if key != nil || keyID != 0 || keyName != "" { 255 return nil, fmt.Errorf("boom: Only one field may be marked id") 256 } 257 keyID = id 258 } else if name, ok := pv.(string); ok { 259 if key != nil || keyID != 0 || keyName != "" { 260 return nil, fmt.Errorf("boom: Only one field may be marked id") 261 } 262 keyName = name 263 } else if propertyKey, ok := pv.(datastore.Key); ok { 264 if key != nil || keyID != 0 || keyName != "" { 265 return nil, fmt.Errorf("boom: Only one field may be marked id") 266 } 267 key = propertyKey 268 } 269 } else { 270 switch vf.Kind() { 271 case reflect.Int64: 272 if key != nil || keyID != 0 || keyName != "" { 273 return nil, fmt.Errorf("boom: Only one field may be marked id") 274 } 275 keyID = vf.Int() 276 case reflect.String: 277 if key != nil || keyID != 0 || keyName != "" { 278 return nil, fmt.Errorf("boom: Only one field may be marked id") 279 } 280 keyName = vf.String() 281 default: 282 return nil, fmt.Errorf("boom: ID field must be int64 or string in %v", t.Name()) 283 } 284 } 285 286 case "kind": 287 if vf.Kind() == reflect.String { 288 if kind != "" { 289 return nil, fmt.Errorf("boom: Only one field may be marked kind") 290 } 291 kind = vf.String() 292 if kind == "" && len(tagValues) > 1 && tagValues[1] != "" { 293 kind = tagValues[1] 294 } 295 } 296 297 case "parent": 298 pt, ok := vf.Interface().(datastore.PropertyTranslator) 299 if ok { 300 pv, err := pt.ToPropertyValue(bm.Context) 301 if err != nil { 302 return nil, err 303 } 304 if key, ok := pv.(datastore.Key); ok { 305 if parent != nil { 306 return nil, fmt.Errorf("boom: Only one field may be marked parent") 307 } 308 parent = key 309 } 310 } else { 311 vfType := vf.Type() 312 if !vf.IsNil() && vfType.ConvertibleTo(typeOfKey) { 313 if parent != nil { 314 return nil, fmt.Errorf("boom: Only one field may be marked parent") 315 } 316 parent = vf.Convert(typeOfKey).Interface().(datastore.Key) 317 } 318 } 319 } 320 } 321 } 322 323 if kind == "" { 324 kind = t.Name() 325 } 326 327 if key != nil { 328 if key.ParentKey() != nil && parent != nil { 329 return nil, fmt.Errorf("boom: ID field returns key. don't use parent annotated field at same time") 330 } 331 if key.Kind() != kind { 332 return nil, fmt.Errorf("boom: ID field returns key that contains unexpected kind") 333 } 334 335 if key.ParentKey() != nil { 336 return key, nil 337 } 338 339 if keyName := key.Name(); keyName != "" { 340 return bm.Client.NameKey(kind, keyName, parent), nil 341 } 342 343 return bm.Client.IDKey(kind, key.ID(), parent), nil 344 } 345 346 if keyName != "" { 347 return bm.Client.NameKey(kind, keyName, parent), nil 348 } 349 350 return bm.Client.IDKey(kind, keyID, parent), nil 351 } 352 353 // AllocateID takes a struct whose key has not yet been set as an argument, 354 // allocates the Key of the relevant Kind, and sets it to a struct. 355 func (bm *Boom) AllocateID(src interface{}) (datastore.Key, error) { 356 srcs := []interface{}{src} 357 keys, err := bm.AllocateIDs(srcs) 358 if merr, ok := err.(datastore.MultiError); ok { 359 return nil, merr[0] 360 } else if err != nil { 361 return nil, err 362 } 363 364 return keys[0], nil 365 } 366 367 // AllocateIDs takes a slice of a struct whose key has not yet been set as an argument, 368 // secures the Key of the relevant Kind, and sets it to each struct. 369 func (bm *Boom) AllocateIDs(src interface{}) ([]datastore.Key, error) { 370 v := reflect.Indirect(reflect.ValueOf(src)) 371 if v.Kind() != reflect.Slice { 372 return nil, fmt.Errorf("boom: value must be a slice or pointer-to-slice or incompletekey-slice or string-slice") 373 } 374 l := v.Len() 375 376 keys := make([]datastore.Key, 0, l) 377 structIndex := make([]int, 0, l) 378 for i := 0; i < l; i++ { 379 v := v.Index(i) 380 obj := v.Interface() 381 382 key, ok := obj.(datastore.Key) 383 if ok { 384 keys = append(keys, key) 385 continue 386 } 387 388 kind, ok := obj.(string) 389 if ok { 390 keys = append(keys, bm.Client.IncompleteKey(kind, nil)) 391 continue 392 } 393 394 key, err := bm.KeyError(obj) 395 if err != nil { 396 return nil, err 397 } 398 keys = append(keys, key) 399 structIndex = append(structIndex, i) 400 } 401 402 keys, err := bm.Client.AllocateIDs(bm.Context, keys) 403 if err != nil { 404 return nil, err 405 } 406 407 for _, sIdx := range structIndex { 408 v := v.Index(sIdx) 409 obj := v.Interface() 410 411 err = bm.setStructKey(obj, keys[sIdx]) 412 if err != nil { 413 return nil, err 414 } 415 } 416 417 return keys, nil 418 } 419 420 // Get loads the entity stored for key into dst, which must be a struct pointer or implement PropertyLoadSaver. 421 // key will be extracted from dst. 422 // 423 // If there is no such entity for the key, Get returns ErrNoSuchEntity. 424 // The values of dst's unmatched struct fields are not modified, and matching slice-typed fields are not reset before appending to them. 425 // In particular, it is recommended to pass a pointer to a zero valued struct on each Get call. 426 func (bm *Boom) Get(dst interface{}) error { 427 dsts := []interface{}{dst} 428 err := bm.GetMulti(dsts) 429 if merr, ok := err.(datastore.MultiError); ok { 430 return merr[0] 431 } else if err != nil { 432 return err 433 } 434 435 return nil 436 } 437 438 // GetMulti is a batch version of Get. 439 // key will be extracted from each struct of dst. 440 // 441 // dst must be a []S, []*S, []I or []P, for some struct type S, some interface type I, or some non-interface non-pointer type P such that P or *P implements PropertyLoadSaver. 442 // If an []I, each element must be a valid dst for Get: it must be a struct pointer or implement PropertyLoadSaver. 443 func (bm *Boom) GetMulti(dst interface{}) error { 444 keys, err := bm.extractKeys(dst) 445 if err != nil { 446 return err 447 } 448 449 return bm.Client.GetMulti(bm.Context, keys, dst) 450 } 451 452 // Put saves the entity src into the datastore. 453 // key will be extract from src struct. 454 // src must be a struct pointer or implement PropertyLoadSaver; if a struct pointer then any unexported fields of that struct will be skipped. 455 // If k is an incomplete key, the returned key will be a unique key generated by the datastore, 456 // and inject key to src struct. 457 func (bm *Boom) Put(src interface{}) (datastore.Key, error) { 458 srcs := []interface{}{src} 459 keys, err := bm.PutMulti(srcs) 460 if merr, ok := err.(datastore.MultiError); ok { 461 return nil, merr[0] 462 } else if err != nil { 463 return nil, err 464 } 465 466 return keys[0], nil 467 } 468 469 // PutMulti is a batch version of Put. 470 // 471 // src must satisfy the same conditions as the dst argument to GetMulti. 472 func (bm *Boom) PutMulti(src interface{}) ([]datastore.Key, error) { 473 keys, err := bm.extractKeys(src) 474 if err != nil { 475 return nil, err 476 } 477 478 keys, err = bm.Client.PutMulti(bm.Context, keys, src) 479 if err != nil { 480 return nil, err 481 } 482 483 v := reflect.Indirect(reflect.ValueOf(src)) 484 for idx, key := range keys { 485 err = bm.setStructKey(v.Index(idx).Interface(), key) 486 if err != nil { 487 return nil, err 488 } 489 } 490 491 return keys, nil 492 } 493 494 // Delete deletes the entity. 495 // key will be extract from src struct. 496 func (bm *Boom) Delete(src interface{}) error { 497 srcs := []interface{}{src} 498 err := bm.DeleteMulti(srcs) 499 if merr, ok := err.(datastore.MultiError); ok { 500 return merr[0] 501 } else if err != nil { 502 return err 503 } 504 505 return nil 506 } 507 508 // DeleteMulti is a batch version of Delete. 509 func (bm *Boom) DeleteMulti(src interface{}) error { 510 keys, err := bm.extractKeys(src) 511 if err != nil { 512 return err 513 } 514 515 return bm.Client.DeleteMulti(bm.Context, keys) 516 } 517 518 // NewTransaction starts a new transaction. 519 func (bm *Boom) NewTransaction() (*Transaction, error) { 520 tx, err := bm.Client.NewTransaction(bm.Context) 521 if err != nil { 522 return nil, err 523 } 524 525 return &Transaction{bm: bm, tx: tx}, nil 526 } 527 528 // RunInTransaction runs f in a transaction. f is invoked with a Transaction that f should use for all the transaction's datastore operations. 529 // 530 // f must not call Commit or Rollback on the provided Transaction. 531 // 532 // If f returns nil, RunInTransaction commits the transaction, returning the Commit and a nil error if it succeeds. 533 // If the commit fails due to a conflicting transaction, RunInTransaction gives up and returns ErrConcurrentTransaction immediately. 534 // If you want to retry operation, You have to retry by yourself. 535 // 536 // If f returns non-nil, then the transaction will be rolled back and RunInTransaction will return the same error. 537 // 538 // Note that when f returns, the transaction is not committed. Calling code must not assume that any of f's changes have been committed until RunInTransaction returns nil. 539 func (bm *Boom) RunInTransaction(f func(tx *Transaction) error) (datastore.Commit, error) { 540 var tx *Transaction 541 commit, err := bm.Client.RunInTransaction(bm.Context, func(origTx datastore.Transaction) error { 542 tx = &Transaction{bm: bm, tx: origTx} 543 return f(tx) 544 }) 545 if err != nil { 546 return nil, err 547 } 548 549 for _, s := range tx.pendingKeysLater { 550 key := commit.Key(s.pendingKey) 551 err = tx.bm.setStructKey(s.src, key) 552 if err != nil { 553 return nil, err 554 } 555 } 556 557 return commit, nil 558 } 559 560 // Run runs the given query. 561 func (bm *Boom) Run(q datastore.Query) *Iterator { 562 it := bm.Client.Run(bm.Context, q) 563 return &Iterator{bm: bm, it: it} 564 } 565 566 // Count returns the number of results for the given query. 567 // 568 // The running time and number of API calls made by Count scale linearly with with the sum of the query's offset and limit. 569 // Unless the result count is expected to be small, it is best to specify a limit; otherwise Count will continue until it finishes counting or the provided context expires. 570 func (bm *Boom) Count(q datastore.Query) (int, error) { 571 return bm.Client.Count(bm.Context, q) 572 } 573 574 // GetAll runs the provided query that it returns all entities that match that query, as well as appending the values to dst. 575 // 576 // dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non-interface, non-pointer type P such that P or *P implements PropertyLoadSaver. 577 // 578 // As a special case, *PropertyList is an invalid type for dst, even though a PropertyList is a slice of structs. 579 // It is treated as invalid to avoid being mistakenly passed when *[]PropertyList was intended. 580 // 581 // The keys are injected to each dst struct. 582 // 583 // If q is a “keys-only” query, GetAll ignores dst and only returns the keys. 584 // 585 // The running time and number of API calls made by GetAll scale linearly with with the sum of the query's offset and limit. 586 // Unless the result count is expected to be small, it is best to specify a limit; otherwise GetAll will continue until it finishes collecting results or the provided context expires. 587 func (bm *Boom) GetAll(q datastore.Query, dst interface{}) ([]datastore.Key, error) { 588 keys, err := bm.Client.GetAll(bm.Context, q, dst) 589 if err != nil { 590 return nil, err 591 } 592 593 if dst == nil { 594 return keys, nil 595 } 596 597 v := reflect.Indirect(reflect.ValueOf(dst)) 598 for idx, key := range keys { 599 err = bm.setStructKey(v.Index(idx).Interface(), key) 600 if err != nil { 601 return nil, err 602 } 603 } 604 605 return keys, nil 606 } 607 608 // Batch creates batch mode objects. 609 func (bm *Boom) Batch() *Batch { 610 b := bm.Client.Batch() 611 return &Batch{bm: bm, b: b} 612 } 613 614 // DecodeCursor from its base-64 string representation. 615 func (bm *Boom) DecodeCursor(s string) (datastore.Cursor, error) { 616 return bm.Client.DecodeCursor(s) 617 } 618 619 // NewQuery creates a new Query for a specific entity kind. 620 // 621 // An empty kind means to return all entities, including entities created and managed by other App Engine features, and is called a kindless query. 622 // Kindless queries cannot include filters or sort orders on property values. 623 func (bm *Boom) NewQuery(k string) datastore.Query { 624 return bm.Client.NewQuery(k) 625 }