Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Side by Side Diff: impl/cloud/datastore.go

Issue 2342063003: Differentiate between single- and multi- props. (Closed)
Patch Set: Slice is now always a clone. This is marginally worse performance, but a much safer UI. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « filter/txnBuf/txnbuf_test.go ('k') | impl/cloud/datastore_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package cloud 5 package cloud
6 6
7 import ( 7 import (
8 "fmt" 8 "fmt"
9 "reflect" 9 "reflect"
10 "strings" 10 "strings"
(...skipping 246 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 func (bds *boundDatastore) prepareNativeQuery(fq *ds.FinalizedQuery) *datastore. Query { 257 func (bds *boundDatastore) prepareNativeQuery(fq *ds.FinalizedQuery) *datastore. Query {
258 nq := datastore.NewQuery(fq.Kind()) 258 nq := datastore.NewQuery(fq.Kind())
259 if bds.transaction != nil { 259 if bds.transaction != nil {
260 nq = nq.Transaction(bds.transaction) 260 nq = nq.Transaction(bds.transaction)
261 } 261 }
262 262
263 // nativeFilter translates a filter field. If the translation fails, we' ll 263 // nativeFilter translates a filter field. If the translation fails, we' ll
264 // pass the result through to the underlying datastore and allow it to 264 // pass the result through to the underlying datastore and allow it to
265 // reject it. 265 // reject it.
266 nativeFilter := func(prop ds.Property) interface{} { 266 nativeFilter := func(prop ds.Property) interface{} {
267 » » if np, err := bds.gaePropertyToNative("", []ds.Property{prop}); err == nil { 267 » » if np, err := bds.gaePropertyToNative("", prop); err == nil {
268 return np.Value 268 return np.Value
269 } 269 }
270 return prop.Value() 270 return prop.Value()
271 } 271 }
272 272
273 // Equality filters. 273 // Equality filters.
274 for field, props := range fq.EqFilters() { 274 for field, props := range fq.EqFilters() {
275 for _, prop := range props { 275 for _, prop := range props {
276 nq = nq.Filter(fmt.Sprintf("%s =", field), nativeFilter( prop)) 276 nq = nq.Filter(fmt.Sprintf("%s =", field), nativeFilter( prop))
277 } 277 }
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
326 nq = nq.Order(prop) 326 nq = nq.Order(prop)
327 } 327 }
328 328
329 return nq 329 return nq
330 } 330 }
331 331
332 func (bds *boundDatastore) mkNPLS(base ds.PropertyMap) *nativePropertyLoadSaver { 332 func (bds *boundDatastore) mkNPLS(base ds.PropertyMap) *nativePropertyLoadSaver {
333 return &nativePropertyLoadSaver{bds: bds, pmap: clonePropertyMap(base)} 333 return &nativePropertyLoadSaver{bds: bds, pmap: clonePropertyMap(base)}
334 } 334 }
335 335
336 func (bds *boundDatastore) gaePropertyToNative(name string, props []ds.Property) (nativeProp datastore.Property, err error) { 336 func (bds *boundDatastore) gaePropertyToNative(name string, pdata ds.PropertyDat a) (nativeProp datastore.Property, err error) {
337 nativeProp.Name = name 337 nativeProp.Name = name
338 338
339 » nativeValues := make([]interface{}, len(props)) 339 » convert := func(prop *ds.Property) (interface{}, error) {
340 » for i, prop := range props {
341 switch pt := prop.Type(); pt { 340 switch pt := prop.Type(); pt {
342 case ds.PTNull, ds.PTInt, ds.PTTime, ds.PTBool, ds.PTBytes, ds.P TString, ds.PTFloat: 341 case ds.PTNull, ds.PTInt, ds.PTTime, ds.PTBool, ds.PTBytes, ds.P TString, ds.PTFloat:
343 » » » nativeValues[i] = prop.Value() 342 » » » return prop.Value(), nil
344 » » » break
345 343
346 case ds.PTKey: 344 case ds.PTKey:
347 » » » nativeValues[i] = bds.gaeKeysToNative(prop.Value().(*ds. Key))[0] 345 » » » return bds.gaeKeysToNative(prop.Value().(*ds.Key))[0], n il
348 346
349 default: 347 default:
350 » » » err = fmt.Errorf("unsupported property type at %d: %v", i, pt) 348 » » » return nil, fmt.Errorf("unsupported property type: %v", pt)
351 » » » return
352 } 349 }
353 } 350 }
354 351
355 » if len(nativeValues) == 1 { 352 » switch t := pdata.(type) {
356 » » nativeProp.Value = nativeValues[0] 353 » case ds.Property:
357 » » nativeProp.NoIndex = (props[0].IndexSetting() != ds.ShouldIndex) 354 » » if nativeProp.Value, err = convert(&t); err != nil {
358 » } else { 355 » » » return
359 » » // We must always index list values. 356 » » }
360 » » nativeProp.Value = nativeValues 357 » » nativeProp.NoIndex = (t.IndexSetting() != ds.ShouldIndex)
358
359 » case ds.PropertySlice:
360 » » // Don't index by default. If *any* sub-property requests being indexed,
361 » » // then we will index.
362 » » nativeProp.NoIndex = true
363
364 » » // Pack this into an interface{} so it is marked as a multi-valu e.
365 » » multiProp := make([]interface{}, len(t))
366 » » for i := range t {
367 » » » prop := &t[i]
368 » » » if multiProp[i], err = convert(prop); err != nil {
369 » » » » return
370 » » » }
371
372 » » » if prop.IndexSetting() == ds.ShouldIndex {
373 » » » » nativeProp.NoIndex = false
374 » » » }
375 » » }
376 » » nativeProp.Value = multiProp
377
378 » default:
379 » » err = fmt.Errorf("unsupported PropertyData type for %q: %T", nam e, pdata)
361 } 380 }
381
362 return 382 return
363 } 383 }
364 384
365 func (bds *boundDatastore) nativePropertyToGAE(nativeProp datastore.Property) (n ame string, props []ds.Property, err error) { 385 func (bds *boundDatastore) nativePropertyToGAE(nativeProp datastore.Property) (n ame string, pdata ds.PropertyData, err error) {
366 name = nativeProp.Name 386 name = nativeProp.Name
367 387
368 » var nativeValues []interface{} 388 » convert := func(nv interface{}, prop *ds.Property) error {
369 » // Slice of supported native type. Break this into a slice of datastore 389 » » switch nvt := nv.(type) {
370 » // properties. 390 » » case nil:
371 » // 391 » » » nv = nil
372 » // It must be an []interface{}.
373 » if rv := reflect.ValueOf(nativeProp.Value); rv.Kind() == reflect.Slice & & rv.Type().Elem().Kind() == reflect.Interface {
374 » » nativeValues = rv.Interface().([]interface{})
375 » } else {
376 » » nativeValues = []interface{}{nativeProp.Value}
377 » }
378 392
379 if len(nativeValues) == 0 {
380 return
381 }
382
383 props = make([]ds.Property, len(nativeValues))
384 for i, nv := range nativeValues {
385 switch nvt := nv.(type) {
386 case int64, bool, string, float64, []byte: 393 case int64, bool, string, float64, []byte:
387 break 394 break
388 395
389 case time.Time: 396 case time.Time:
390 // Cloud datastore library returns local time. 397 // Cloud datastore library returns local time.
391 nv = nvt.UTC() 398 nv = nvt.UTC()
392 399
393 case *datastore.Key: 400 case *datastore.Key:
394 nv = bds.nativeKeysToGAE(nvt)[0] 401 nv = bds.nativeKeysToGAE(nvt)[0]
395 402
396 default: 403 default:
397 » » » err = fmt.Errorf("element %d has unsupported datastore.V alue type %T", i, nv) 404 » » » return fmt.Errorf("unsupported datastore.Value type for %q: %T", name, nvt)
398 » » » return
399 } 405 }
400 406
401 indexSetting := ds.ShouldIndex 407 indexSetting := ds.ShouldIndex
402 if nativeProp.NoIndex { 408 if nativeProp.NoIndex {
403 indexSetting = ds.NoIndex 409 indexSetting = ds.NoIndex
404 } 410 }
405 » » props[i].SetValue(nv, indexSetting) 411 » » prop.SetValue(nv, indexSetting)
412 » » return nil
406 } 413 }
414
415 // Slice of supported native type. Break this into a slice of datastore
416 // properties.
417 //
418 // It must be an []interface{}.
419 if rv := reflect.ValueOf(nativeProp.Value); rv.Kind() == reflect.Slice & & rv.Type().Elem().Kind() == reflect.Interface {
420 // []interface{}, which is a multi-valued property with a single name.
421 // Convert to a PropertySlice.
422 nativeValues := rv.Interface().([]interface{})
423 pslice := make(ds.PropertySlice, len(nativeValues))
424 for i, nv := range nativeValues {
425 if err = convert(nv, &pslice[i]); err != nil {
426 return
427 }
428 }
429 pdata = pslice
430 return
431 }
432
433 var prop ds.Property
434 if err = convert(nativeProp.Value, &prop); err != nil {
435 return
436 }
437 pdata = prop
407 return 438 return
408 } 439 }
409 440
410 func (bds *boundDatastore) gaeKeysToNative(keys ...*ds.Key) []*datastore.Key { 441 func (bds *boundDatastore) gaeKeysToNative(keys ...*ds.Key) []*datastore.Key {
411 nativeKeys := make([]*datastore.Key, len(keys)) 442 nativeKeys := make([]*datastore.Key, len(keys))
412 for i, key := range keys { 443 for i, key := range keys {
413 _, _, toks := key.Split() 444 _, _, toks := key.Split()
414 445
415 var nativeKey *datastore.Key 446 var nativeKey *datastore.Key
416 for _, tok := range toks { 447 for _, tok := range toks {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
456 487
457 var _ datastore.PropertyLoadSaver = (*nativePropertyLoadSaver)(nil) 488 var _ datastore.PropertyLoadSaver = (*nativePropertyLoadSaver)(nil)
458 489
459 func (npls *nativePropertyLoadSaver) Load(props []datastore.Property) error { 490 func (npls *nativePropertyLoadSaver) Load(props []datastore.Property) error {
460 if npls.pmap == nil { 491 if npls.pmap == nil {
461 // Allocate for common case: one property per property name. 492 // Allocate for common case: one property per property name.
462 npls.pmap = make(ds.PropertyMap, len(props)) 493 npls.pmap = make(ds.PropertyMap, len(props))
463 } 494 }
464 495
465 for _, nativeProp := range props { 496 for _, nativeProp := range props {
466 » » name, props, err := npls.bds.nativePropertyToGAE(nativeProp) 497 » » name, pdata, err := npls.bds.nativePropertyToGAE(nativeProp)
467 if err != nil { 498 if err != nil {
468 return err 499 return err
469 } 500 }
470 » » npls.pmap[name] = append(npls.pmap[name], props...) 501 » » if _, ok := npls.pmap[name]; ok {
502 » » » return fmt.Errorf("duplicate properties for %q", name)
503 » » }
504 » » npls.pmap[name] = pdata
471 } 505 }
472 return nil 506 return nil
473 } 507 }
474 508
475 func (npls *nativePropertyLoadSaver) Save() ([]datastore.Property, error) { 509 func (npls *nativePropertyLoadSaver) Save() ([]datastore.Property, error) {
476 if len(npls.pmap) == 0 { 510 if len(npls.pmap) == 0 {
477 return nil, nil 511 return nil, nil
478 } 512 }
479 513
480 props := make([]datastore.Property, 0, len(npls.pmap)) 514 props := make([]datastore.Property, 0, len(npls.pmap))
481 » for name, plist := range npls.pmap { 515 » for name, pdata := range npls.pmap {
482 // Strip meta. 516 // Strip meta.
483 if strings.HasPrefix(name, "$") { 517 if strings.HasPrefix(name, "$") {
484 continue 518 continue
485 } 519 }
486 520
487 » » nativeProp, err := npls.bds.gaePropertyToNative(name, plist) 521 » » nativeProp, err := npls.bds.gaePropertyToNative(name, pdata)
488 if err != nil { 522 if err != nil {
489 return nil, err 523 return nil, err
490 } 524 }
491 props = append(props, nativeProp) 525 props = append(props, nativeProp)
492 } 526 }
493 return props, nil 527 return props, nil
494 } 528 }
495 529
496 var datastoreTransactionKey = "*datastore.Transaction" 530 var datastoreTransactionKey = "*datastore.Transaction"
497 531
498 func withDatastoreTransaction(c context.Context, tx *datastore.Transaction) cont ext.Context { 532 func withDatastoreTransaction(c context.Context, tx *datastore.Transaction) cont ext.Context {
499 return context.WithValue(c, &datastoreTransactionKey, tx) 533 return context.WithValue(c, &datastoreTransactionKey, tx)
500 } 534 }
501 535
502 func datastoreTransaction(c context.Context) *datastore.Transaction { 536 func datastoreTransaction(c context.Context) *datastore.Transaction {
503 if tx, ok := c.Value(&datastoreTransactionKey).(*datastore.Transaction); ok { 537 if tx, ok := c.Value(&datastoreTransactionKey).(*datastore.Transaction); ok {
504 return tx 538 return tx
505 } 539 }
506 return nil 540 return nil
507 } 541 }
508 542
509 func clonePropertyMap(pmap ds.PropertyMap) ds.PropertyMap { 543 func clonePropertyMap(pmap ds.PropertyMap) ds.PropertyMap {
510 if pmap == nil { 544 if pmap == nil {
511 return nil 545 return nil
512 } 546 }
513 547
514 clone := make(ds.PropertyMap, len(pmap)) 548 clone := make(ds.PropertyMap, len(pmap))
515 » for k, props := range pmap { 549 » for k, pdata := range pmap {
516 » » clone[k] = append([]ds.Property(nil), props...) 550 » » clone[k] = pdata.Clone()
517 } 551 }
518 return clone 552 return clone
519 } 553 }
520 554
521 func normalizeError(err error) error { 555 func normalizeError(err error) error {
522 switch err { 556 switch err {
523 case datastore.ErrNoSuchEntity: 557 case datastore.ErrNoSuchEntity:
524 return ds.ErrNoSuchEntity 558 return ds.ErrNoSuchEntity
525 case datastore.ErrConcurrentTransaction: 559 case datastore.ErrConcurrentTransaction:
526 return ds.ErrConcurrentTransaction 560 return ds.ErrConcurrentTransaction
527 case datastore.ErrInvalidKey: 561 case datastore.ErrInvalidKey:
528 return ds.ErrInvalidKey 562 return ds.ErrInvalidKey
529 default: 563 default:
530 return err 564 return err
531 } 565 }
532 } 566 }
OLDNEW
« no previous file with comments | « filter/txnBuf/txnbuf_test.go ('k') | impl/cloud/datastore_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698