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

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: fix comments 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
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 ErrMultipleInequalityFilter = errors.New(
18 "inequality filters on multiple properties in the same Query is not allo wed")
19
20 var ErrNullQuery = errors.New(
21 "the query is overconstraind and can never have results")
22
23 type Query struct {
24 kind string
25
26 eventualConsistency bool
27 keysOnly bool
28 distinct bool
29
30 limit *int32
31 offset *int32
32
33 order []IndexColumn
34 project stringset.Set
35
36 eqFilts map[string]PropertySlice
37
38 ineqFiltProp string
39 ineqFiltLow Property
40 ineqFiltLowIncl bool
41 ineqFiltLowSet bool
42 ineqFiltHigh Property
43 ineqFiltHighIncl bool
44 ineqFiltHighSet bool
45
46 start Cursor
47 end Cursor
48
49 // These are set by Finalize as a way to cache the 1-1 correspondence of
50 // a Query to its FinalizedQuery form. err may also be set by intermedia te
51 // Query functions if there's a problem before finalization.
52 finalized *FinalizedQuery
53 err error
54 }
55
56 func NewQuery(kind string) *Query {
57 return &Query{kind: kind}
58 }
59
60 func (q *Query) mod(cb func(*Query)) *Query {
61 if q.err != nil {
62 return q
63 }
64
65 ret := *q
66 ret.finalized = nil
67 if len(q.order) > 0 {
68 ret.order = make([]IndexColumn, len(q.order))
69 copy(ret.order, q.order)
70 }
71 if q.project != nil {
72 ret.project = q.project.Dup()
73 }
74 if len(q.eqFilts) > 0 {
75 ret.eqFilts = make(map[string]PropertySlice, len(q.eqFilts))
76 for k, v := range q.eqFilts {
77 newV := make(PropertySlice, len(v))
78 copy(newV, v)
79 ret.eqFilts[k] = newV
80 }
81 }
82 cb(&ret)
83 return &ret
84 }
85
86 func (q *Query) Kind(kind string) *Query {
87 return q.mod(func(q *Query) {
88 q.kind = kind
89 })
90 }
91
92 func (q *Query) Ancestor(ancestor *Key) *Query {
93 return q.mod(func(q *Query) {
94 if q.eqFilts == nil {
95 q.eqFilts = map[string]PropertySlice{}
96 }
97 if ancestor == nil {
98 delete(q.eqFilts, "__ancestor__")
99 if len(q.eqFilts) == 0 {
100 q.eqFilts = nil
101 }
102 } else {
103 q.eqFilts["__ancestor__"] = PropertySlice{MkProperty(anc estor)}
104 }
105 })
106 }
107
108 func (q *Query) EventualConsistency(on bool) *Query {
109 return q.mod(func(q *Query) {
110 q.eventualConsistency = on
111 })
112 }
113
114 func (q *Query) Limit(limit int32) *Query {
115 return q.mod(func(q *Query) {
116 if limit < 0 {
117 q.limit = nil
118 } else {
119 q.limit = &limit
120 }
121 })
122 }
123
124 func (q *Query) Offset(offset int32) *Query {
125 return q.mod(func(q *Query) {
126 if offset < 0 {
127 q.offset = nil
128 } else {
129 q.offset = &offset
130 }
131 })
132 }
133
134 func (q *Query) KeysOnly(on bool) *Query {
135 return q.mod(func(q *Query) {
136 q.keysOnly = on
137 })
138 }
139
140 func (q *Query) Order(fieldNames ...string) *Query {
141 if len(fieldNames) == 0 {
142 return q
143 }
144 return q.mod(func(q *Query) {
145 for _, fn := range fieldNames {
146 ic, err := ParseIndexColumn(fn)
147 if err != nil {
148 q.err = err
149 return
150 }
151 if q.reserved(ic.Property) {
152 return
153 }
154 q.order = append(q.order, ic)
155 }
156 })
157 }
158
159 func (q *Query) ClearOrder() *Query {
160 return q.mod(func(q *Query) {
161 q.order = nil
162 })
163 }
164
165 func (q *Query) Project(fieldNames ...string) *Query {
166 if len(fieldNames) == 0 {
167 return q
168 }
169 return q.mod(func(q *Query) {
170 for _, f := range fieldNames {
171 if q.reserved(f) {
172 return
173 }
174 if f == "__key__" {
175 q.err = fmt.Errorf("cannot project on %q", f)
176 return
177 }
178 if q.project == nil {
179 q.project = stringset.New(1)
180 }
181 q.project.Add(f)
182 }
183 })
184 }
185
186 func (q *Query) Distinct(on bool) *Query {
187 return q.mod(func(q *Query) {
188 q.distinct = on
189 })
190 }
191
192 func (q *Query) ClearProject() *Query {
193 return q.mod(func(q *Query) {
194 q.project = nil
195 })
196 }
197
198 func (q *Query) Start(c Cursor) *Query {
199 return q.mod(func(q *Query) {
200 q.start = c
201 })
202 }
203
204 func (q *Query) End(c Cursor) *Query {
205 return q.mod(func(q *Query) {
206 q.end = c
207 })
208 }
209
210 func (q *Query) Eq(field string, values ...interface{}) *Query {
211 return q.mod(func(q *Query) {
212 if !q.reserved(field) {
213 if q.eqFilts == nil {
214 q.eqFilts = make(map[string]PropertySlice, 1)
215 }
216 s := q.eqFilts[field]
217 for _, value := range values {
218 p := Property{}
219 if q.err = p.SetValue(value, ShouldIndex); q.err != nil {
220 return
221 }
222 idx := sort.Search(len(s), func(i int) bool {
223 return s[i].Equal(&p)
224 })
225 if idx == len(s) {
226 s = append(s, p)
227 sort.Sort(s)
228 }
229 }
230 q.eqFilts[field] = s
231 }
232 })
233 }
234
235 func (q *Query) reserved(field string) bool {
236 if field == "__key__" {
237 return false
238 }
239 if field == "" {
240 q.err = fmt.Errorf(
241 "cannot filter/project on: %q", field)
242 return true
243 }
244 if strings.HasPrefix(field, "__") && strings.HasSuffix(field, "__") {
245 q.err = fmt.Errorf(
246 "cannot filter/project on reserved property: %q", field)
247 return true
248 }
249 return false
250 }
251
252 func (q *Query) ineqOK(field string, value Property) bool {
253 if q.reserved(field) {
254 return false
255 }
256 if field == "__key__" && value.Type() != PTKey {
257 q.err = fmt.Errorf(
258 "filters on %q must have type *Key (got %s)", field, val ue.Type())
259 return false
260 }
261 if q.ineqFiltProp != "" && q.ineqFiltProp != field {
262 q.err = ErrMultipleInequalityFilter
263 return false
264 }
265 return true
266 }
267
268 func (q *Query) Lt(field string, value interface{}) *Query {
269 p := Property{}
270 err := p.SetValue(value, ShouldIndex)
271
272 if err == nil && q.ineqFiltHighSet {
273 if q.ineqFiltHigh.Less(&p) {
274 return q
275 } else if q.ineqFiltHigh.Equal(&p) && !q.ineqFiltHighIncl {
276 return q
277 }
278 }
279
280 return q.mod(func(q *Query) {
281 if q.err = err; err != nil {
282 return
283 }
284 if q.ineqOK(field, p) {
285 q.ineqFiltProp = field
286 q.ineqFiltHighSet = true
287 q.ineqFiltHigh = p
288 q.ineqFiltHighIncl = false
289 }
290 })
291 }
292
293 func (q *Query) Lte(field string, value interface{}) *Query {
294 p := Property{}
295 err := p.SetValue(value, ShouldIndex)
296
297 if err == nil && q.ineqFiltHighSet {
298 if q.ineqFiltHigh.Less(&p) {
299 return q
300 } else if q.ineqFiltHigh.Equal(&p) {
301 return q
302 }
303 }
304
305 return q.mod(func(q *Query) {
306 if q.err = err; err != nil {
307 return
308 }
309 if q.ineqOK(field, p) {
310 q.ineqFiltProp = field
311 q.ineqFiltHighSet = true
312 q.ineqFiltHigh = p
313 q.ineqFiltHighIncl = true
314 }
315 })
316 }
317
318 func (q *Query) Gt(field string, value interface{}) *Query {
319 p := Property{}
320 err := p.SetValue(value, ShouldIndex)
321
322 if err == nil && q.ineqFiltLowSet {
323 if p.Less(&q.ineqFiltLow) {
324 return q
325 } else if p.Equal(&q.ineqFiltLow) && !q.ineqFiltLowIncl {
326 return q
327 }
328 }
329
330 return q.mod(func(q *Query) {
331 if q.err = err; err != nil {
332 return
333 }
334 if q.ineqOK(field, p) {
335 q.ineqFiltProp = field
336 q.ineqFiltLowSet = true
337 q.ineqFiltLow = p
338 q.ineqFiltLowIncl = false
339 }
340 })
341 }
342
343 func (q *Query) Gte(field string, value interface{}) *Query {
344 p := Property{}
345 err := p.SetValue(value, ShouldIndex)
346
347 if err == nil && q.ineqFiltLowSet {
348 if p.Less(&q.ineqFiltLow) {
349 return q
350 } else if p.Equal(&q.ineqFiltLow) {
351 return q
352 }
353 }
354
355 return q.mod(func(q *Query) {
356 if q.err = err; err != nil {
357 return
358 }
359 if q.ineqOK(field, p) {
360 q.ineqFiltProp = field
361 q.ineqFiltLowSet = true
362 q.ineqFiltLow = p
363 q.ineqFiltLowIncl = true
364 }
365 })
366 }
367
368 func (q *Query) ClearFilters() *Query {
369 return q.mod(func(q *Query) {
370 anc := q.eqFilts["__ancestor__"]
371 if anc != nil {
372 q.eqFilts = map[string]PropertySlice{"__ancestor__": anc }
373 } else {
374 q.eqFilts = nil
375 }
376 q.ineqFiltLowSet = false
377 q.ineqFiltHighSet = false
378 })
379 }
380
381 func (q *Query) Finalize() (*FinalizedQuery, error) {
382 if q.err != nil || q.finalized != nil {
383 return q.finalized, q.err
384 }
385
386 err := func() error {
387 if q.kind == "" { // kindless query checks
388 if q.ineqFiltProp != "" && q.ineqFiltProp != "__key__" {
389 return fmt.Errorf(
390 "kindless queries can only filter on __k ey__, got %q", q.ineqFiltProp)
391 }
392 if len(q.eqFilts) > 0 {
393 return fmt.Errorf("kindless queries not have any equality filters")
394 }
395 for _, o := range q.order {
396 if o.Property != "__key__" || o.Direction != ASC ENDING {
397 return fmt.Errorf("invalid order for kin dless query: %#v", o)
398 }
399 }
400 }
401
402 if q.keysOnly && q.project != nil && q.project.Len() > 0 {
403 return errors.New("cannot project a keysOnly query")
404 }
405
406 if q.ineqFiltProp != "" {
407 if len(q.order) > 0 && q.order[0].Property != q.ineqFilt Prop {
408 return fmt.Errorf(
409 "first sort order must match inequality filter: %q v %q",
410 q.order[0].Property, q.ineqFiltProp)
411 }
412 if q.ineqFiltLowSet && q.ineqFiltHighSet {
413 if q.ineqFiltHigh.Less(&q.ineqFiltLow) &&
414 (q.ineqFiltHigh.Equal(&q.ineqFiltLow) &&
415 (!q.ineqFiltLowIncl || !q.ineqFi ltHighIncl)) {
416 return ErrNullQuery
417 }
418 }
419 }
420
421 err := error(nil)
422 if q.project != nil {
423 q.project.Iter(func(p string) bool {
424 if _, iseq := q.eqFilts[p]; iseq {
425 err = fmt.Errorf("cannot project on equa lity filter field: %s", p)
426 return false
427 }
428 return true
429 })
430 }
431 return err
432 }()
433 if err != nil {
434 q.err = err
435 return nil, err
436 }
437
438 ret := &FinalizedQuery{
439 original: q,
440 kind: q.kind,
441
442 keysOnly: q.keysOnly,
443 eventuallyConsistent: q.eventualConsistency || q.eqFilts["__ance stor__"] == nil,
444 limit: q.limit,
445 offset: q.offset,
446 start: q.start,
447 end: q.end,
448
449 eqFilts: q.eqFilts,
450
451 ineqFiltProp: q.ineqFiltProp,
452 ineqFiltLow: q.ineqFiltLow,
453 ineqFiltLowIncl: q.ineqFiltLowIncl,
454 ineqFiltLowSet: q.ineqFiltLowSet,
455 ineqFiltHigh: q.ineqFiltHigh,
456 ineqFiltHighIncl: q.ineqFiltHighIncl,
457 ineqFiltHighSet: q.ineqFiltHighSet,
458 }
459
460 if q.project != nil {
461 ret.project = q.project.ToSlice()
462 ret.distinct = q.distinct && q.project.Len() > 0
463 }
464
465 // if len(q.order) > 0, we already enforce that the first order
466 // is the same as the inequality above. Otherwise we need to add it.
467 if len(q.order) == 0 && q.ineqFiltProp != "" {
468 ret.orders = []IndexColumn{{q.ineqFiltProp, ASCENDING}}
469 }
470
471 seenOrders := stringset.New(len(q.order))
472
473 // drop orders where there's an equality filter
474 // https://cloud.google.com/appengine/docs/go/datastore/queries#sort_o rders_are_ignored_on_properties_with_equality_filters
475 // Deduplicate orders
476 for _, o := range q.order {
477 if _, iseq := q.eqFilts[o.Property]; !iseq {
478 if seenOrders.Add(o.Property) {
479 ret.orders = append(ret.orders, o)
480 }
481 }
482 }
483
484 // Add any projection columns not mentioned in the user-defined order as
485 // ASCENDING orders. Technically we could be smart and automatically use
486 // a DESCENDING ordered index, if it fit, but the logic gets insane, sin ce all
487 // suffixes of all used indexes need to be PRECISELY equal (and so you'd have
488 // to hunt/invalidate/something to find the combination of indexes that are
489 // compatible with each other as well as the query). If you want to use
490 // a DESCENDING column, just add it to the user sort order, and this loo p will
491 // not synthesize a new suffix entry for it.
492 //
493 // NOTE: if you want to use an index that sorts by -__key__, you MUST
494 // include all of the projected fields for that index in the order expli citly.
495 // Otherwise the generated orders will be wacky. So:
496 // Query("Foo").Project("A", "B").Order("A").Order("-__key__")
497 //
498 // will turn into a orders of:
499 // A, ASCENDING
500 // __key__, DESCENDING
501 // B, ASCENDING
502 // __key__, ASCENDING
503 //
504 // To prevent this, your query should have another Order("B") clause bef ore
505 // the -__key__ clause.
506 if len(ret.project) > 0 {
507 sort.Strings(ret.project)
508 for _, p := range ret.project {
509 if !seenOrders.Has(p) {
510 ret.orders = append(ret.orders, IndexColumn{p, A SCENDING})
511 }
512 }
513 }
514
515 // If the suffix format ends with __key__ already (e.g. .Order("__key__" )),
516 // then we're good to go. Otherwise we need to add it as the last bit of the
517 // suffix, since all indexes implicitly have it as the last column.
518 if len(ret.orders) == 0 || ret.orders[len(ret.orders)-1].Property != "__ key__" {
519 ret.orders = append(ret.orders, IndexColumn{"__key__", ASCENDING })
520 }
521
522 q.finalized = ret
523 return ret, nil
524 }
525
526 func (q *Query) String() string {
527 ret := &bytes.Buffer{}
528 needComma := false
529 p := func(fmtStr string, stuff ...interface{}) {
530 if needComma {
531 ret.WriteString(", ")
532 }
533 needComma = true
534 fmt.Fprintf(ret, fmtStr, stuff...)
535 }
536 ret.WriteString("Query(")
537 if q.err != nil {
538 p("ERROR=%q", q.err.Error())
539 }
540
541 // Filters
542 if q.kind != "" {
543 p("Kind=%q", q.kind)
544 }
545 if q.eqFilts["__ancestor__"] != nil {
546 p("Ancestor=%s", q.eqFilts["__ancestor__"][0].Value().(*Key).Str ing())
547 }
548 for prop, vals := range q.eqFilts {
549 if prop == "__ancestor__" {
550 continue
551 }
552 for _, v := range vals {
553 p("Filter(%q == %s)", prop, v.GQL())
554 }
555 }
556 if q.ineqFiltProp != "" {
557 if q.ineqFiltLowSet {
558 op := ">"
559 if q.ineqFiltLowIncl {
560 op = ">="
561 }
562 p("Filter(%q %s %s)", q.ineqFiltProp, op, q.ineqFiltLow. GQL())
563 }
564 if q.ineqFiltHighSet {
565 op := "<"
566 if q.ineqFiltHighIncl {
567 op = "<="
568 }
569 p("Filter(%q %s %s)", q.ineqFiltProp, op, q.ineqFiltHigh .GQL())
570 }
571 }
572
573 // Order
574 if len(q.order) > 0 {
575 orders := make([]string, len(q.order))
576 for i, o := range q.order {
577 orders[i] = o.String()
578 }
579 p("Order(%s)", strings.Join(orders, ", "))
580 }
581
582 // Projection
583 if q.project != nil && q.project.Len() > 0 {
584 f := "Project(%s)"
585 if q.distinct {
586 f = "Project[DISTINCT](%s)"
587 }
588 p(f, strings.Join(q.project.ToSlice(), ", "))
589 }
590
591 // Cursors
592 if q.start != nil {
593 p("Start(%q)", q.start.String())
594 }
595 if q.end != nil {
596 p("End(%q)", q.end.String())
597 }
598
599 // Modifiiers
600 if q.limit != nil {
601 p("Limit=%d", *q.limit)
602 }
603 if q.offset != nil {
604 p("Offset=%d", *q.offset)
605 }
606 if q.eventualConsistency {
607 p("EventualConsistency")
608 }
609 if q.keysOnly {
610 p("KeysOnly")
611 }
612
613 ret.WriteRune(')')
614
615 return ret.String()
616 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698