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

Side by Side Diff: service/datastore/query.go

Issue 1910293002: Remove race from query finalization. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/gae@master
Patch Set: Created 4 years, 8 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 | « no previous file | service/datastore/query_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 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package datastore 5 package datastore
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "fmt" 9 "fmt"
10 "sort" 10 "sort"
11 "strings" 11 "strings"
12 "sync"
12 13
13 "github.com/luci/luci-go/common/errors" 14 "github.com/luci/luci-go/common/errors"
14 "github.com/luci/luci-go/common/stringset" 15 "github.com/luci/luci-go/common/stringset"
15 ) 16 )
16 17
17 var ( 18 var (
18 // ErrMultipleInequalityFilter is returned from Query.Finalize if you bu ild a 19 // ErrMultipleInequalityFilter is returned from Query.Finalize if you bu ild a
19 // query which has inequality filters on multiple fields. 20 // query which has inequality filters on multiple fields.
20 ErrMultipleInequalityFilter = errors.New( 21 ErrMultipleInequalityFilter = errors.New(
21 "inequality filters on multiple properties in the same Query is not allowed") 22 "inequality filters on multiple properties in the same Query is not allowed")
22 23
23 // ErrNullQuery is returned from Query.Finalize if you build a query for which 24 // ErrNullQuery is returned from Query.Finalize if you build a query for which
24 // there cannot possibly be any results. 25 // there cannot possibly be any results.
25 ErrNullQuery = errors.New( 26 ErrNullQuery = errors.New(
26 "the query is overconstrained and can never have results") 27 "the query is overconstrained and can never have results")
27 ) 28 )
28 29
29 // Query is a builder-object for building a datastore query. It may represent 30 // Query is a builder-object for building a datastore query. It may represent
30 // an invalid query, but the error will only be observable when you call 31 // an invalid query, but the error will only be observable when you call
31 // Finalize. 32 // Finalize.
33 //
34 // A Query is, for the most part, not goroutine-safe. However, it is
32 type Query struct { 35 type Query struct {
36 queryFields
37
38 // These are set by Finalize as a way to cache the 1-1 correspondence of
39 // a Query to its FinalizedQuery form. err may also be set by intermedia te
40 // Query functions if there's a problem before finalization.
41 //
42 // Query implements lazy finalization, meaning that it will happen at mo st
43 // once. This means that the finalization state and cached finalization must
44 // be locked around.
45 finalizeOnce sync.Once
46 finalized *FinalizedQuery
47 finalizeErr error
48 }
49
50 // queryFields are the Query's read-only fields.
51 type queryFields struct {
dnj 2016/04/21 22:49:16 Why!? Because we clone the query by copying it, m
33 kind string 52 kind string
34 53
35 eventualConsistency bool 54 eventualConsistency bool
36 keysOnly bool 55 keysOnly bool
37 distinct bool 56 distinct bool
38 57
39 limit *int32 58 limit *int32
40 offset *int32 59 offset *int32
41 60
42 order []IndexColumn 61 order []IndexColumn
43 project stringset.Set 62 project stringset.Set
44 63
45 eqFilts map[string]PropertySlice 64 eqFilts map[string]PropertySlice
46 65
47 ineqFiltProp string 66 ineqFiltProp string
48 ineqFiltLow Property 67 ineqFiltLow Property
49 ineqFiltLowIncl bool 68 ineqFiltLowIncl bool
50 ineqFiltLowSet bool 69 ineqFiltLowSet bool
51 ineqFiltHigh Property 70 ineqFiltHigh Property
52 ineqFiltHighIncl bool 71 ineqFiltHighIncl bool
53 ineqFiltHighSet bool 72 ineqFiltHighSet bool
54 73
55 start Cursor 74 start Cursor
56 end Cursor 75 end Cursor
57 76
58 » // These are set by Finalize as a way to cache the 1-1 correspondence of 77 » err error
59 » // a Query to its FinalizedQuery form. err may also be set by intermedia te
60 » // Query functions if there's a problem before finalization.
61 » finalized *FinalizedQuery
62 » err error
63 } 78 }
64 79
65 // NewQuery returns a new Query for the given kind. If kind may be empty to 80 // NewQuery returns a new Query for the given kind. If kind may be empty to
66 // begin a kindless query. 81 // begin a kindless query.
67 func NewQuery(kind string) *Query { 82 func NewQuery(kind string) *Query {
68 » return &Query{kind: kind} 83 » return &Query{
84 » » queryFields: queryFields{
85 » » » kind: kind,
86 » » },
87 » }
69 } 88 }
70 89
71 func (q *Query) mod(cb func(*Query)) *Query { 90 func (q *Query) mod(cb func(*Query)) *Query {
72 if q.err != nil { 91 if q.err != nil {
73 return q 92 return q
74 } 93 }
75 94
76 » ret := *q 95 » ret := Query{
77 » ret.finalized = nil 96 » » queryFields: q.queryFields,
97 » }
78 if len(q.order) > 0 { 98 if len(q.order) > 0 {
79 ret.order = make([]IndexColumn, len(q.order)) 99 ret.order = make([]IndexColumn, len(q.order))
80 copy(ret.order, q.order) 100 copy(ret.order, q.order)
81 } 101 }
82 if q.project != nil { 102 if q.project != nil {
83 ret.project = q.project.Dup() 103 ret.project = q.project.Dup()
84 } 104 }
85 if len(q.eqFilts) > 0 { 105 if len(q.eqFilts) > 0 {
86 ret.eqFilts = make(map[string]PropertySlice, len(q.eqFilts)) 106 ret.eqFilts = make(map[string]PropertySlice, len(q.eqFilts))
87 for k, v := range q.eqFilts { 107 for k, v := range q.eqFilts {
(...skipping 373 matching lines...) Expand 10 before | Expand all | Expand 10 after
461 } 481 }
462 q.ineqFiltLowSet = false 482 q.ineqFiltLowSet = false
463 q.ineqFiltHighSet = false 483 q.ineqFiltHighSet = false
464 }) 484 })
465 } 485 }
466 486
467 // Finalize converts this Query to a FinalizedQuery. If the Query has any 487 // Finalize converts this Query to a FinalizedQuery. If the Query has any
468 // inconsistencies or violates any of the query rules, that will be returned 488 // inconsistencies or violates any of the query rules, that will be returned
469 // here. 489 // here.
470 func (q *Query) Finalize() (*FinalizedQuery, error) { 490 func (q *Query) Finalize() (*FinalizedQuery, error) {
471 » if q.err != nil || q.finalized != nil { 491 » if q.err != nil {
472 » » return q.finalized, q.err 492 » » return nil, q.err
473 } 493 }
474 494
495 q.finalizeOnce.Do(func() {
496 q.finalized, q.finalizeErr = q.finalizeImpl()
497 })
498 return q.finalized, q.finalizeErr
499 }
500
501 func (q *Query) finalizeImpl() (*FinalizedQuery, error) {
475 ancestor := (*Key)(nil) 502 ancestor := (*Key)(nil)
476 if slice, ok := q.eqFilts["__ancestor__"]; ok { 503 if slice, ok := q.eqFilts["__ancestor__"]; ok {
477 ancestor = slice[0].Value().(*Key) 504 ancestor = slice[0].Value().(*Key)
478 } 505 }
479 506
480 err := func() error { 507 err := func() error {
481 508
482 if q.kind == "" { // kindless query checks 509 if q.kind == "" { // kindless query checks
483 if q.ineqFiltProp != "" && q.ineqFiltProp != "__key__" { 510 if q.ineqFiltProp != "" && q.ineqFiltProp != "__key__" {
484 return fmt.Errorf( 511 return fmt.Errorf(
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 if _, iseq := q.eqFilts[p]; iseq { 564 if _, iseq := q.eqFilts[p]; iseq {
538 err = fmt.Errorf("cannot project on equa lity filter field: %s", p) 565 err = fmt.Errorf("cannot project on equa lity filter field: %s", p)
539 return false 566 return false
540 } 567 }
541 return true 568 return true
542 }) 569 })
543 } 570 }
544 return err 571 return err
545 }() 572 }()
546 if err != nil { 573 if err != nil {
547 q.err = err
548 return nil, err 574 return nil, err
549 } 575 }
550 576
551 ret := &FinalizedQuery{ 577 ret := &FinalizedQuery{
552 original: q, 578 original: q,
553 kind: q.kind, 579 kind: q.kind,
554 580
555 keysOnly: q.keysOnly, 581 keysOnly: q.keysOnly,
556 eventuallyConsistent: q.eventualConsistency || ancestor == nil, 582 eventuallyConsistent: q.eventualConsistency || ancestor == nil,
557 limit: q.limit, 583 limit: q.limit,
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
637 } 663 }
638 } 664 }
639 665
640 // If the suffix format ends with __key__ already (e.g. .Order("__key__" )), 666 // If the suffix format ends with __key__ already (e.g. .Order("__key__" )),
641 // then we're good to go. Otherwise we need to add it as the last bit of the 667 // then we're good to go. Otherwise we need to add it as the last bit of the
642 // suffix, since all indexes implicitly have it as the last column. 668 // suffix, since all indexes implicitly have it as the last column.
643 if len(ret.orders) == 0 || ret.orders[len(ret.orders)-1].Property != "__ key__" { 669 if len(ret.orders) == 0 || ret.orders[len(ret.orders)-1].Property != "__ key__" {
644 ret.orders = append(ret.orders, IndexColumn{Property: "__key__"} ) 670 ret.orders = append(ret.orders, IndexColumn{Property: "__key__"} )
645 } 671 }
646 672
647 q.finalized = ret
648 return ret, nil 673 return ret, nil
649 } 674 }
650 675
651 func (q *Query) String() string { 676 func (q *Query) String() string {
652 ret := &bytes.Buffer{} 677 ret := &bytes.Buffer{}
653 needComma := false 678 needComma := false
654 p := func(fmtStr string, stuff ...interface{}) { 679 p := func(fmtStr string, stuff ...interface{}) {
655 if needComma { 680 if needComma {
656 if _, err := ret.WriteString(", "); err != nil { 681 if _, err := ret.WriteString(", "); err != nil {
657 panic(err) 682 panic(err)
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
738 if q.keysOnly { 763 if q.keysOnly {
739 p("KeysOnly") 764 p("KeysOnly")
740 } 765 }
741 766
742 if _, err := ret.WriteRune(')'); err != nil { 767 if _, err := ret.WriteRune(')'); err != nil {
743 panic(err) 768 panic(err)
744 } 769 }
745 770
746 return ret.String() 771 return ret.String()
747 } 772 }
OLDNEW
« no previous file with comments | « no previous file | service/datastore/query_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698