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

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

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: appease errcheck Created 5 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 | « service/datastore/properties_test.go ('k') | 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
(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 "fmt"
10 "sort"
11 "strings"
12
13 "github.com/luci/luci-go/common/errors"
14 "github.com/luci/luci-go/common/stringset"
15 )
16
17 var (
18 // ErrMultipleInequalityFilter is returned from Query.Finalize if you bu ild a
19 // query which has inequality filters on multiple fields.
20 ErrMultipleInequalityFilter = errors.New(
21 "inequality filters on multiple properties in the same Query is not allowed")
22
23 // ErrNullQuery is returned from Query.Finalize if you build a query for which
24 // there cannot possibly be any results.
25 ErrNullQuery = errors.New(
26 "the query is overconstrained and can never have results")
27 )
28
29 // 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 // Finalize.
32 type Query struct {
33 kind string
34
35 eventualConsistency bool
36 keysOnly bool
37 distinct bool
38
39 limit *int32
40 offset *int32
41
42 order []IndexColumn
43 project stringset.Set
44
45 eqFilts map[string]PropertySlice
46
47 ineqFiltProp string
48 ineqFiltLow Property
49 ineqFiltLowIncl bool
50 ineqFiltLowSet bool
51 ineqFiltHigh Property
52 ineqFiltHighIncl bool
53 ineqFiltHighSet bool
54
55 start Cursor
56 end Cursor
57
58 // These are set by Finalize as a way to cache the 1-1 correspondence of
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 }
64
65 // NewQuery returns a new Query for the given kind. If kind may be empty to
66 // begin a kindless query.
67 func NewQuery(kind string) *Query {
68 return &Query{kind: kind}
69 }
70
71 func (q *Query) mod(cb func(*Query)) *Query {
72 if q.err != nil {
73 return q
74 }
75
76 ret := *q
77 ret.finalized = nil
78 if len(q.order) > 0 {
79 ret.order = make([]IndexColumn, len(q.order))
80 copy(ret.order, q.order)
81 }
82 if q.project != nil {
83 ret.project = q.project.Dup()
84 }
85 if len(q.eqFilts) > 0 {
86 ret.eqFilts = make(map[string]PropertySlice, len(q.eqFilts))
87 for k, v := range q.eqFilts {
88 newV := make(PropertySlice, len(v))
89 copy(newV, v)
90 ret.eqFilts[k] = newV
91 }
92 }
93 cb(&ret)
94 return &ret
95 }
96
97 // Kind alters the kind of this query.
98 func (q *Query) Kind(kind string) *Query {
99 return q.mod(func(q *Query) {
100 q.kind = kind
101 })
102 }
103
104 // Ancestor sets the ancestor filter for this query.
105 //
106 // If ancestor is nil, then this removes the Ancestor restriction from the
107 // query.
108 func (q *Query) Ancestor(ancestor *Key) *Query {
109 return q.mod(func(q *Query) {
110 if q.eqFilts == nil {
111 q.eqFilts = map[string]PropertySlice{}
112 }
113 if ancestor == nil {
114 delete(q.eqFilts, "__ancestor__")
115 if len(q.eqFilts) == 0 {
116 q.eqFilts = nil
117 }
118 } else {
119 q.eqFilts["__ancestor__"] = PropertySlice{MkProperty(anc estor)}
120 }
121 })
122 }
123
124 // EventualConsistency changes the EventualConsistency setting for this query.
125 //
126 // It only has an effect on Ancestor queries and is otherwise ignored.
127 func (q *Query) EventualConsistency(on bool) *Query {
128 return q.mod(func(q *Query) {
129 q.eventualConsistency = on
130 })
131 }
132
133 // Limit sets the limit (max items to return) for this query. If limit < 0, this
134 // removes the limit from the query entirely.
135 func (q *Query) Limit(limit int32) *Query {
136 return q.mod(func(q *Query) {
137 if limit < 0 {
138 q.limit = nil
139 } else {
140 q.limit = &limit
141 }
142 })
143 }
144
145 // Offset sets the offset (number of items to skip) for this query. If
146 // offset < 0, this removes the offset from the query entirely.
147 func (q *Query) Offset(offset int32) *Query {
148 return q.mod(func(q *Query) {
149 if offset < 0 {
150 q.offset = nil
151 } else {
152 q.offset = &offset
153 }
154 })
155 }
156
157 // KeysOnly makes this into a query which only returns keys (but doesn't fetch
158 // values). It's incompatible with projection queries.
159 func (q *Query) KeysOnly(on bool) *Query {
160 return q.mod(func(q *Query) {
161 q.keysOnly = on
162 })
163 }
164
165 // Order sets one or more orders for this query.
166 func (q *Query) Order(fieldNames ...string) *Query {
167 if len(fieldNames) == 0 {
168 return q
169 }
170 return q.mod(func(q *Query) {
171 for _, fn := range fieldNames {
172 ic, err := ParseIndexColumn(fn)
173 if err != nil {
174 q.err = err
175 return
176 }
177 if q.reserved(ic.Property) {
178 return
179 }
180 q.order = append(q.order, ic)
181 }
182 })
183 }
184
185 // ClearOrder removes all orders from this Query.
186 func (q *Query) ClearOrder() *Query {
187 return q.mod(func(q *Query) {
188 q.order = nil
189 })
190 }
191
192 // Project lists one or more field names to project.
193 func (q *Query) Project(fieldNames ...string) *Query {
194 if len(fieldNames) == 0 {
195 return q
196 }
197 return q.mod(func(q *Query) {
198 for _, f := range fieldNames {
199 if q.reserved(f) {
200 return
201 }
202 if f == "__key__" {
203 q.err = fmt.Errorf("cannot project on %q", f)
204 return
205 }
206 if q.project == nil {
207 q.project = stringset.New(1)
208 }
209 q.project.Add(f)
210 }
211 })
212 }
213
214 // Distinct makes a projection query only return distinct values. This has
215 // no effect on non-projection queries.
216 func (q *Query) Distinct(on bool) *Query {
217 return q.mod(func(q *Query) {
218 q.distinct = on
219 })
220 }
221
222 // ClearProject removes all projected fields from this Query.
223 func (q *Query) ClearProject() *Query {
224 return q.mod(func(q *Query) {
225 q.project = nil
226 })
227 }
228
229 // Start sets a starting cursor. The cursor is implementation-defined by the
230 // particular 'impl' you have installed.
231 func (q *Query) Start(c Cursor) *Query {
232 return q.mod(func(q *Query) {
233 q.start = c
234 })
235 }
236
237 // End sets the ending cursor. The cursor is implementation-defined by the
238 // particular 'impl' you have installed.
239 func (q *Query) End(c Cursor) *Query {
240 return q.mod(func(q *Query) {
241 q.end = c
242 })
243 }
244
245 // Eq adds one or more equality restrictions to the query.
246 //
247 // Equality filters interact with multiply-defined properties by ensuring that
248 // the given field has /at least one/ value which is equal to the specified
249 // constraint.
250 //
251 // So a query with `.Eq("thing", 1, 2)` will only return entities where the
252 // field "thing" is multiply defined and contains both a value of 1 and a value
253 // of 2.
254 //
255 // `Eq("thing", 1).Eq("thing", 2)` and `.Eq("thing", 1, 2)` have identical
256 // meaning.
257 func (q *Query) Eq(field string, values ...interface{}) *Query {
258 if len(values) == 0 {
259 return q
260 }
261 return q.mod(func(q *Query) {
262 if !q.reserved(field) {
263 if q.eqFilts == nil {
264 q.eqFilts = make(map[string]PropertySlice, 1)
265 }
266 s := q.eqFilts[field]
267 for _, value := range values {
268 p := Property{}
269 if q.err = p.SetValue(value, ShouldIndex); q.err != nil {
270 return
271 }
272 idx := sort.Search(len(s), func(i int) bool {
273 return s[i].Equal(&p)
274 })
275 if idx == len(s) {
276 s = append(s, p)
277 sort.Sort(s)
278 }
279 }
280 q.eqFilts[field] = s
281 }
282 })
283 }
284
285 func (q *Query) reserved(field string) bool {
286 if field == "__key__" {
287 return false
288 }
289 if field == "" {
290 q.err = fmt.Errorf(
291 "cannot filter/project on: %q", field)
292 return true
293 }
294 if strings.HasPrefix(field, "__") && strings.HasSuffix(field, "__") {
295 q.err = fmt.Errorf(
296 "cannot filter/project on reserved property: %q", field)
297 return true
298 }
299 return false
300 }
301
302 func (q *Query) ineqOK(field string, value Property) bool {
303 if q.reserved(field) {
304 return false
305 }
306 if field == "__key__" && value.Type() != PTKey {
307 q.err = fmt.Errorf(
308 "filters on %q must have type *Key (got %s)", field, val ue.Type())
309 return false
310 }
311 if q.ineqFiltProp != "" && q.ineqFiltProp != field {
312 q.err = ErrMultipleInequalityFilter
313 return false
314 }
315 return true
316 }
317
318 // Lt imposes a 'less-than' inequality restriction on the Query.
319 //
320 // Inequality filters interact with multiply-defined properties by ensuring that
321 // the given field has /exactly one/ value which matches /all/ of the inequality
322 // constraints.
323 //
324 // So a query with `.Gt("thing", 5).Lt("thing", 10)` will only return entities
325 // where the field "thing" has a single value where `5 < val < 10`.
326 func (q *Query) Lt(field string, value interface{}) *Query {
327 p := Property{}
328 err := p.SetValue(value, ShouldIndex)
329
330 if err == nil && q.ineqFiltHighSet {
331 if q.ineqFiltHigh.Less(&p) {
332 return q
333 } else if q.ineqFiltHigh.Equal(&p) && !q.ineqFiltHighIncl {
334 return q
335 }
336 }
337
338 return q.mod(func(q *Query) {
339 if q.err = err; err != nil {
340 return
341 }
342 if q.ineqOK(field, p) {
343 q.ineqFiltProp = field
344 q.ineqFiltHighSet = true
345 q.ineqFiltHigh = p
346 q.ineqFiltHighIncl = false
347 }
348 })
349 }
350
351 // Lte imposes a 'less-than-or-equal' inequality restriction on the Query.
352 //
353 // Inequality filters interact with multiply-defined properties by ensuring that
354 // the given field has /exactly one/ value which matches /all/ of the inequality
355 // constraints.
356 //
357 // So a query with `.Gt("thing", 5).Lt("thing", 10)` will only return entities
358 // where the field "thing" has a single value where `5 < val < 10`.
359 func (q *Query) Lte(field string, value interface{}) *Query {
360 p := Property{}
361 err := p.SetValue(value, ShouldIndex)
362
363 if err == nil && q.ineqFiltHighSet {
364 if q.ineqFiltHigh.Less(&p) {
365 return q
366 } else if q.ineqFiltHigh.Equal(&p) {
367 return q
368 }
369 }
370
371 return q.mod(func(q *Query) {
372 if q.err = err; err != nil {
373 return
374 }
375 if q.ineqOK(field, p) {
376 q.ineqFiltProp = field
377 q.ineqFiltHighSet = true
378 q.ineqFiltHigh = p
379 q.ineqFiltHighIncl = true
380 }
381 })
382 }
383
384 // Gt imposes a 'greater-than' inequality restriction on the Query.
385 //
386 // Inequality filters interact with multiply-defined properties by ensuring that
387 // the given field has /exactly one/ value which matches /all/ of the inequality
388 // constraints.
389 //
390 // So a query with `.Gt("thing", 5).Lt("thing", 10)` will only return entities
391 // where the field "thing" has a single value where `5 < val < 10`.
392 func (q *Query) Gt(field string, value interface{}) *Query {
393 p := Property{}
394 err := p.SetValue(value, ShouldIndex)
395
396 if err == nil && q.ineqFiltLowSet {
397 if p.Less(&q.ineqFiltLow) {
398 return q
399 } else if p.Equal(&q.ineqFiltLow) && !q.ineqFiltLowIncl {
400 return q
401 }
402 }
403
404 return q.mod(func(q *Query) {
405 if q.err = err; err != nil {
406 return
407 }
408 if q.ineqOK(field, p) {
409 q.ineqFiltProp = field
410 q.ineqFiltLowSet = true
411 q.ineqFiltLow = p
412 q.ineqFiltLowIncl = false
413 }
414 })
415 }
416
417 // Gte imposes a 'greater-than-or-equal' inequality restriction on the Query.
418 //
419 // Inequality filters interact with multiply-defined properties by ensuring that
420 // the given field has /exactly one/ value which matches /all/ of the inequality
421 // constraints.
422 //
423 // So a query with `.Gt("thing", 5).Lt("thing", 10)` will only return entities
424 // where the field "thing" has a single value where `5 < val < 10`.
425 func (q *Query) Gte(field string, value interface{}) *Query {
426 p := Property{}
427 err := p.SetValue(value, ShouldIndex)
428
429 if err == nil && q.ineqFiltLowSet {
430 if p.Less(&q.ineqFiltLow) {
431 return q
432 } else if p.Equal(&q.ineqFiltLow) {
433 return q
434 }
435 }
436
437 return q.mod(func(q *Query) {
438 if q.err = err; err != nil {
439 return
440 }
441 if q.ineqOK(field, p) {
442 q.ineqFiltProp = field
443 q.ineqFiltLowSet = true
444 q.ineqFiltLow = p
445 q.ineqFiltLowIncl = true
446 }
447 })
448 }
449
450 // ClearFilters clears all equality and inequality filters from the Query. It
451 // does not clear the Ancestor filter if one is defined.
452 func (q *Query) ClearFilters() *Query {
453 return q.mod(func(q *Query) {
454 anc := q.eqFilts["__ancestor__"]
455 if anc != nil {
456 q.eqFilts = map[string]PropertySlice{"__ancestor__": anc }
457 } else {
458 q.eqFilts = nil
459 }
460 q.ineqFiltLowSet = false
461 q.ineqFiltHighSet = false
462 })
463 }
464
465 // Finalize converts this Query to a FinalizedQuery. If the Query has any
466 // inconsistencies or violates any of the query rules, that will be returned
467 // here.
468 func (q *Query) Finalize() (*FinalizedQuery, error) {
469 if q.err != nil || q.finalized != nil {
470 return q.finalized, q.err
471 }
472
473 err := func() error {
474 if q.kind == "" { // kindless query checks
475 if q.ineqFiltProp != "" && q.ineqFiltProp != "__key__" {
476 return fmt.Errorf(
477 "kindless queries can only filter on __k ey__, got %q", q.ineqFiltProp)
478 }
479 if len(q.eqFilts) > 0 {
480 return fmt.Errorf("kindless queries not have any equality filters")
481 }
482 for _, o := range q.order {
483 if o.Property != "__key__" || o.Descending {
484 return fmt.Errorf("invalid order for kin dless query: %#v", o)
485 }
486 }
487 }
488
489 if q.keysOnly && q.project != nil && q.project.Len() > 0 {
490 return errors.New("cannot project a keysOnly query")
491 }
492
493 if q.ineqFiltProp != "" {
494 if len(q.order) > 0 && q.order[0].Property != q.ineqFilt Prop {
495 return fmt.Errorf(
496 "first sort order must match inequality filter: %q v %q",
497 q.order[0].Property, q.ineqFiltProp)
498 }
499 if q.ineqFiltLowSet && q.ineqFiltHighSet {
500 if q.ineqFiltHigh.Less(&q.ineqFiltLow) &&
501 (q.ineqFiltHigh.Equal(&q.ineqFiltLow) &&
502 (!q.ineqFiltLowIncl || !q.ineqFi ltHighIncl)) {
503 return ErrNullQuery
504 }
505 }
506 }
507
508 err := error(nil)
509 if q.project != nil {
510 q.project.Iter(func(p string) bool {
511 if _, iseq := q.eqFilts[p]; iseq {
512 err = fmt.Errorf("cannot project on equa lity filter field: %s", p)
513 return false
514 }
515 return true
516 })
517 }
518 return err
519 }()
520 if err != nil {
521 q.err = err
522 return nil, err
523 }
524
525 ret := &FinalizedQuery{
526 original: q,
527 kind: q.kind,
528
529 keysOnly: q.keysOnly,
530 eventuallyConsistent: q.eventualConsistency || q.eqFilts["__ance stor__"] == nil,
531 limit: q.limit,
532 offset: q.offset,
533 start: q.start,
534 end: q.end,
535
536 eqFilts: q.eqFilts,
537
538 ineqFiltProp: q.ineqFiltProp,
539 ineqFiltLow: q.ineqFiltLow,
540 ineqFiltLowIncl: q.ineqFiltLowIncl,
541 ineqFiltLowSet: q.ineqFiltLowSet,
542 ineqFiltHigh: q.ineqFiltHigh,
543 ineqFiltHighIncl: q.ineqFiltHighIncl,
544 ineqFiltHighSet: q.ineqFiltHighSet,
545 }
546
547 if q.project != nil {
548 ret.project = q.project.ToSlice()
549 ret.distinct = q.distinct && q.project.Len() > 0
550 }
551
552 // if len(q.order) > 0, we already enforce that the first order
553 // is the same as the inequality above. Otherwise we need to add it.
554 if len(q.order) == 0 && q.ineqFiltProp != "" {
555 ret.orders = []IndexColumn{{Property: q.ineqFiltProp}}
556 }
557
558 seenOrders := stringset.New(len(q.order))
559
560 // drop orders where there's an equality filter
561 // https://cloud.google.com/appengine/docs/go/datastore/queries#sort_o rders_are_ignored_on_properties_with_equality_filters
562 // Deduplicate orders
563 for _, o := range q.order {
564 if _, iseq := q.eqFilts[o.Property]; !iseq {
565 if seenOrders.Add(o.Property) {
566 ret.orders = append(ret.orders, o)
567 }
568 }
569 }
570
571 // Add any projection columns not mentioned in the user-defined order as
572 // ASCENDING orders. Technically we could be smart and automatically use
573 // a DESCENDING ordered index, if it fit, but the logic gets insane, sin ce all
574 // suffixes of all used indexes need to be PRECISELY equal (and so you'd have
575 // to hunt/invalidate/something to find the combination of indexes that are
576 // compatible with each other as well as the query). If you want to use
577 // a DESCENDING column, just add it to the user sort order, and this loo p will
578 // not synthesize a new suffix entry for it.
579 //
580 // NOTE: if you want to use an index that sorts by -__key__, you MUST
581 // include all of the projected fields for that index in the order expli citly.
582 // Otherwise the generated orders will be wacky. So:
583 // Query("Foo").Project("A", "B").Order("A").Order("-__key__")
584 //
585 // will turn into a orders of:
586 // A, ASCENDING
587 // __key__, DESCENDING
588 // B, ASCENDING
589 // __key__, ASCENDING
590 //
591 // To prevent this, your query should have another Order("B") clause bef ore
592 // the -__key__ clause.
593 if len(ret.project) > 0 {
594 sort.Strings(ret.project)
595 for _, p := range ret.project {
596 if !seenOrders.Has(p) {
597 ret.orders = append(ret.orders, IndexColumn{Prop erty: p})
598 }
599 }
600 }
601
602 // If the suffix format ends with __key__ already (e.g. .Order("__key__" )),
603 // then we're good to go. Otherwise we need to add it as the last bit of the
604 // suffix, since all indexes implicitly have it as the last column.
605 if len(ret.orders) == 0 || ret.orders[len(ret.orders)-1].Property != "__ key__" {
606 ret.orders = append(ret.orders, IndexColumn{Property: "__key__"} )
607 }
608
609 q.finalized = ret
610 return ret, nil
611 }
612
613 func (q *Query) String() string {
614 ret := &bytes.Buffer{}
615 needComma := false
616 p := func(fmtStr string, stuff ...interface{}) {
617 if needComma {
618 if _, err := ret.WriteString(", "); err != nil {
619 panic(err)
620 }
621 }
622 needComma = true
623 fmt.Fprintf(ret, fmtStr, stuff...)
624 }
625 if _, err := ret.WriteString("Query("); err != nil {
626 panic(err)
627 }
628 if q.err != nil {
629 p("ERROR=%q", q.err.Error())
630 }
631
632 // Filters
633 if q.kind != "" {
634 p("Kind=%q", q.kind)
635 }
636 if q.eqFilts["__ancestor__"] != nil {
637 p("Ancestor=%s", q.eqFilts["__ancestor__"][0].Value().(*Key).Str ing())
638 }
639 for prop, vals := range q.eqFilts {
640 if prop == "__ancestor__" {
641 continue
642 }
643 for _, v := range vals {
644 p("Filter(%q == %s)", prop, v.GQL())
645 }
646 }
647 if q.ineqFiltProp != "" {
648 if q.ineqFiltLowSet {
649 op := ">"
650 if q.ineqFiltLowIncl {
651 op = ">="
652 }
653 p("Filter(%q %s %s)", q.ineqFiltProp, op, q.ineqFiltLow. GQL())
654 }
655 if q.ineqFiltHighSet {
656 op := "<"
657 if q.ineqFiltHighIncl {
658 op = "<="
659 }
660 p("Filter(%q %s %s)", q.ineqFiltProp, op, q.ineqFiltHigh .GQL())
661 }
662 }
663
664 // Order
665 if len(q.order) > 0 {
666 orders := make([]string, len(q.order))
667 for i, o := range q.order {
668 orders[i] = o.String()
669 }
670 p("Order(%s)", strings.Join(orders, ", "))
671 }
672
673 // Projection
674 if q.project != nil && q.project.Len() > 0 {
675 f := "Project(%s)"
676 if q.distinct {
677 f = "Project[DISTINCT](%s)"
678 }
679 p(f, strings.Join(q.project.ToSlice(), ", "))
680 }
681
682 // Cursors
683 if q.start != nil {
684 p("Start(%q)", q.start.String())
685 }
686 if q.end != nil {
687 p("End(%q)", q.end.String())
688 }
689
690 // Modifiiers
691 if q.limit != nil {
692 p("Limit=%d", *q.limit)
693 }
694 if q.offset != nil {
695 p("Offset=%d", *q.offset)
696 }
697 if q.eventualConsistency {
698 p("EventualConsistency")
699 }
700 if q.keysOnly {
701 p("KeysOnly")
702 }
703
704 if _, err := ret.WriteRune(')'); err != nil {
705 panic(err)
706 }
707
708 return ret.String()
709 }
OLDNEW
« no previous file with comments | « service/datastore/properties_test.go ('k') | service/datastore/query_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698