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

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: 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 q.eqFilts != nil {
dnj 2015/09/18 16:47:58 len(q.eqFilts) > 0
iannucci 2015/09/18 22:25:49 done
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 {
dnj 2015/09/18 16:47:58 Worth doing pre-mod identity checks for these? e.g
iannucci 2015/09/18 22:25:48 Not really. If you have allocation bottlenecks in
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 return q.mod(func(q *Query) {
142 for _, fn := range fieldNames {
143 ic, err := ParseIndexColumn(fn)
144 if err != nil {
145 q.err = err
146 return
147 }
148 if q.reserved(ic.Property) {
149 return
150 }
151 q.order = append(q.order, ic)
152 }
153 })
154 }
155
156 func (q *Query) RevertOrder() *Query {
dnj 2015/09/18 16:47:59 ClearOrder()
157 return q.mod(func(q *Query) {
158 q.order = nil
159 })
160 }
161
162 func (q *Query) Project(fieldNames ...string) *Query {
163 return q.mod(func(q *Query) {
164 for _, f := range fieldNames {
165 if q.reserved(f) {
166 return
167 }
168 if f == "__key__" {
169 q.err = fmt.Errorf("cannot project on %q", f)
170 return
171 }
172 if q.project == nil {
173 q.project = stringset.New(1)
174 }
175 q.project.Add(f)
176 }
177 })
178 }
179
180 func (q *Query) Distinct(on bool) *Query {
181 return q.mod(func(q *Query) {
182 q.distinct = on
183 })
184 }
185
186 func (q *Query) RevertProject() *Query {
dnj 2015/09/18 16:47:59 ClearProject
187 return q.mod(func(q *Query) {
188 q.project = nil
189 })
190 }
191
192 func (q *Query) Start(c Cursor) *Query {
193 return q.mod(func(q *Query) {
194 q.start = c
195 })
196 }
197
198 func (q *Query) End(c Cursor) *Query {
199 return q.mod(func(q *Query) {
200 q.end = c
201 })
202 }
203
204 func (q *Query) Eq(field string, values ...interface{}) *Query {
205 return q.mod(func(q *Query) {
206 if !q.reserved(field) {
207 if q.eqFilts == nil {
208 q.eqFilts = map[string]PropertySlice{}
dnj 2015/09/18 16:47:58 make(..., 1)
iannucci 2015/09/18 22:25:48 has anyone told you that you're the king of warran
209 }
210 s := q.eqFilts[field]
211 for _, value := range values {
212 p := Property{}
213 if q.err = p.SetValue(value, ShouldIndex); q.err != nil {
214 return
215 }
216 idx := sort.Search(len(s), func(i int) bool {
217 return s[i].Equal(&p)
218 })
219 if idx == len(s) {
220 s = append(s, p)
221 sort.Sort(s)
222 }
223 }
224 q.eqFilts[field] = s
225 }
226 })
227 }
228
229 func (q *Query) reserved(field string) bool {
230 if field == "__key__" {
231 return false
232 }
233 if field == "" {
234 q.err = fmt.Errorf(
235 "cannot filter/project on: %q", field)
236 return true
237 }
238 if strings.HasPrefix(field, "__") && strings.HasSuffix(field, "__") {
dnj 2015/09/18 16:47:58 Is "_ _ _" (without spaces) reserved? Granted it's
iannucci 2015/09/18 22:25:49 They're not actually reserved in the underlying th
239 q.err = fmt.Errorf(
240 "cannot filter/project on reserved property: %q", field)
241 return true
242 }
243 return false
244 }
245
246 func (q *Query) ineqOK(field string, value Property) bool {
247 if q.reserved(field) {
248 return false
249 }
250 if field == "__key__" && value.Type() != PTKey {
251 q.err = fmt.Errorf(
252 "filters on %q must have type *Key (got %s)", field, val ue.Type())
253 return false
254 }
255 if q.ineqFiltProp != "" && q.ineqFiltProp != field {
256 q.err = ErrMultipleInequalityFilter
257 return false
258 }
259 return true
260 }
261
262 func (q *Query) Lt(field string, value interface{}) *Query {
263 p := Property{}
264 err := p.SetValue(value, ShouldIndex)
265
266 if err == nil && q.ineqFiltHighSet {
dnj 2015/09/18 16:47:59 IMO the auto-adjustment is convenient, but it shou
iannucci 2015/09/18 22:25:49 yep, it does in q.ineqOK (which modifies q.err as
267 if q.ineqFiltHigh.Less(&p) {
268 return q
269 } else if q.ineqFiltHigh.Equal(&p) && !q.ineqFiltHighIncl {
270 return q
271 }
272 }
273
274 return q.mod(func(q *Query) {
275 if q.err = err; err != nil {
276 return
277 }
278 if q.ineqOK(field, p) {
279 q.ineqFiltProp = field
280 q.ineqFiltHighSet = true
281 q.ineqFiltHigh = p
282 q.ineqFiltHighIncl = false
283 }
284 })
285 }
286
287 func (q *Query) Lte(field string, value interface{}) *Query {
288 p := Property{}
289 err := p.SetValue(value, ShouldIndex)
290
291 if err == nil && q.ineqFiltHighSet {
292 if q.ineqFiltHigh.Less(&p) {
293 return q
294 } else if q.ineqFiltHigh.Equal(&p) {
295 return q
296 }
297 }
298
299 return q.mod(func(q *Query) {
300 if q.err = err; err != nil {
301 return
302 }
303 if q.ineqOK(field, p) {
304 q.ineqFiltProp = field
305 q.ineqFiltHighSet = true
306 q.ineqFiltHigh = p
307 q.ineqFiltHighIncl = true
308 }
309 })
310 }
311
312 func (q *Query) Gt(field string, value interface{}) *Query {
313 p := Property{}
314 err := p.SetValue(value, ShouldIndex)
315
316 if err == nil && q.ineqFiltLowSet {
317 if p.Less(&q.ineqFiltLow) {
318 return q
319 } else if p.Equal(&q.ineqFiltLow) && !q.ineqFiltLowIncl {
320 return q
321 }
322 }
323
324 return q.mod(func(q *Query) {
325 if q.err = err; err != nil {
326 return
327 }
328 if q.ineqOK(field, p) {
329 q.ineqFiltProp = field
330 q.ineqFiltLowSet = true
331 q.ineqFiltLow = p
332 q.ineqFiltLowIncl = false
333 }
334 })
335 }
336
337 func (q *Query) Gte(field string, value interface{}) *Query {
338 p := Property{}
339 err := p.SetValue(value, ShouldIndex)
340
341 if err == nil && q.ineqFiltLowSet {
342 if p.Less(&q.ineqFiltLow) {
343 return q
344 } else if p.Equal(&q.ineqFiltLow) {
345 return q
346 }
347 }
348
349 return q.mod(func(q *Query) {
350 if q.err = err; err != nil {
351 return
352 }
353 if q.ineqOK(field, p) {
354 q.ineqFiltProp = field
355 q.ineqFiltLowSet = true
356 q.ineqFiltLow = p
357 q.ineqFiltLowIncl = true
358 }
359 })
360 }
361
362 func (q *Query) RevertFilters() *Query {
dnj 2015/09/18 16:47:58 ClearFilters
iannucci 2015/09/18 22:25:48 done for all
363 return q.mod(func(q *Query) {
364 anc := q.eqFilts["__ancestor__"]
365 if anc != nil {
366 q.eqFilts = map[string]PropertySlice{"__ancestor__": anc }
367 } else {
368 q.eqFilts = nil
369 }
370 q.ineqFiltLowSet = false
371 q.ineqFiltHighSet = false
372 })
373 }
374
375 func (q *Query) Finalize() (*FinalizedQuery, error) {
376 if q.err != nil || q.finalized != nil {
377 return q.finalized, q.err
378 }
379
380 err := func() error {
381 if q.kind == "" { // kindless query checks
382 if q.ineqFiltProp != "" && q.ineqFiltProp != "__key__" {
383 return fmt.Errorf(
384 "kindless queries can only filter on __k ey__, got %q", q.ineqFiltProp)
385 }
386 if len(q.eqFilts) > 0 {
387 return fmt.Errorf("kindless queries not have any equality filters")
388 }
389 for _, o := range q.order {
390 if o.Property != "__key__" || o.Direction != ASC ENDING {
391 return fmt.Errorf("invalid order for kin dless query: %#v", o)
392 }
393 }
394 }
395
396 if q.keysOnly && q.project != nil && q.project.Len() > 0 {
397 return errors.New("cannot project a keysOnly query")
398 }
399
400 if q.ineqFiltProp != "" {
401 if len(q.order) > 0 && q.order[0].Property != q.ineqFilt Prop {
402 return fmt.Errorf(
403 "first sort order must match inequality filter: %q v %q",
404 q.order[0].Property, q.ineqFiltProp)
405 }
406 if q.ineqFiltLowSet && q.ineqFiltHighSet {
407 if q.ineqFiltHigh.Less(&q.ineqFiltLow) &&
408 (q.ineqFiltHigh.Equal(&q.ineqFiltLow) &&
409 (!q.ineqFiltLowIncl || !q.ineqFi ltHighIncl)) {
410 return ErrNullQuery
411 }
412 }
413 }
414
415 err := error(nil)
416 if q.project != nil {
417 q.project.Iter(func(p string) bool {
418 if _, iseq := q.eqFilts[p]; iseq {
419 err = fmt.Errorf("cannot project on equa lity filter field: %s", p)
420 return false
421 }
422 return true
423 })
424 }
425 return err
426 }()
427 if err != nil {
428 q.err = err
429 return nil, err
430 }
431
432 ret := &FinalizedQuery{
433 original: q,
434 kind: q.kind,
435
436 keysOnly: q.keysOnly,
437 eventuallyConsistent: q.eventualConsistency || q.eqFilts["__ance stor__"] == nil,
438 limit: q.limit,
439 offset: q.offset,
440 start: q.start,
441 end: q.end,
442
443 eqFilts: q.eqFilts,
444
445 ineqFiltProp: q.ineqFiltProp,
446 ineqFiltLow: q.ineqFiltLow,
447 ineqFiltLowIncl: q.ineqFiltLowIncl,
448 ineqFiltLowSet: q.ineqFiltLowSet,
449 ineqFiltHigh: q.ineqFiltHigh,
450 ineqFiltHighIncl: q.ineqFiltHighIncl,
451 ineqFiltHighSet: q.ineqFiltHighSet,
452 }
453
454 if q.project != nil {
455 ret.project = q.project.ToSlice()
456 ret.distinct = q.distinct && q.project.Len() > 0
457 }
458
459 // if len(q.order) > 0, we already enforce that the first order
460 // is the same as the inequality above. Otherwise we need to add it.
461 if len(q.order) == 0 && q.ineqFiltProp != "" {
462 ret.orders = []IndexColumn{{q.ineqFiltProp, ASCENDING}}
463 }
464
465 seenOrders := stringset.New(len(q.order))
466
467 // drop orders where there's an equality filter
468 // https://cloud.google.com/appengine/docs/go/datastore/queries#sort_o rders_are_ignored_on_properties_with_equality_filters
469 // Deduplicate orders
470 for _, o := range q.order {
471 if _, iseq := q.eqFilts[o.Property]; !iseq {
472 if seenOrders.Add(o.Property) {
473 ret.orders = append(ret.orders, o)
474 }
475 }
476 }
477
478 // Add any projection columns not mentioned in the user-defined order as
479 // ASCENDING orders. Technically we could be smart and automatically use
480 // a DESCENDING ordered index, if it fit, but the logic gets insane, sin ce all
481 // suffixes of all used indexes need to be PRECISELY equal (and so you'd have
482 // to hunt/invalidate/something to find the combination of indexes that are
483 // compatible with each other as well as the query). If you want to use
484 // a DESCENDING column, just add it to the user sort order, and this loo p will
485 // not synthesize a new suffix entry for it.
486 //
487 // NOTE: if you want to use an index that sorts by -__key__, you MUST
488 // include all of the projected fields for that index in the order expli citly.
489 // Otherwise the generated orders will be wacky. So:
490 // Query("Foo").Project("A", "B").Order("A").Order("-__key__")
491 //
492 // will turn into a orders of:
493 // A, ASCENDING
494 // __key__, DESCENDING
495 // B, ASCENDING
496 // __key__, ASCENDING
497 //
498 // To prevent this, your query should have another Order("B") clause bef ore
499 // the -__key__ clause.
500 if len(ret.project) > 0 {
501 sort.Strings(ret.project)
502 for _, p := range ret.project {
503 if !seenOrders.Has(p) {
504 ret.orders = append(ret.orders, IndexColumn{p, A SCENDING})
505 }
506 }
507 }
508
509 // If the suffix format ends with __key__ already (e.g. .Order("__key__" )),
510 // then we're good to go. Otherwise we need to add it as the last bit of the
511 // suffix, since all indexes implicitly have it as the last column.
512 if len(ret.orders) == 0 || ret.orders[len(ret.orders)-1].Property != "__ key__" {
513 ret.orders = append(ret.orders, IndexColumn{"__key__", ASCENDING })
514 }
515
516 q.finalized = ret
517 return ret, nil
518 }
519
520 func (q *Query) String() string {
521 ret := &bytes.Buffer{}
522 needComma := false
523 p := func(fmtStr string, stuff ...interface{}) {
524 if needComma {
525 ret.WriteString(", ")
526 }
527 needComma = true
528 fmt.Fprintf(ret, fmtStr, stuff...)
529 }
530 ret.WriteString("Query(")
531 if q.err != nil {
532 p("ERROR=%q", q.err.Error())
533 }
534
535 // Filters
536 if q.kind != "" {
537 p("Kind=%q", q.kind)
538 }
539 if q.eqFilts["__ancestor__"] != nil {
540 p("Ancestor=%s", q.eqFilts["__ancestor__"][0].Value().(*Key).Str ing())
541 }
542 for prop, vals := range q.eqFilts {
543 if prop == "__ancestor__" {
544 continue
545 }
546 for _, v := range vals {
547 p("Filter(%q == %s)", prop, v.GQL())
548 }
549 }
550 if q.ineqFiltProp != "" {
551 if q.ineqFiltLowSet {
552 op := ">"
553 if q.ineqFiltLowIncl {
554 op = ">="
555 }
556 p("Filter(%q %s %s)", q.ineqFiltProp, op, q.ineqFiltLow. GQL())
557 }
dnj 2015/09/18 16:47:58 ineqFiltHighSet?
iannucci 2015/09/18 22:25:48 oops
558 }
559
560 // Order
561 if len(q.order) > 0 {
562 orders := make([]string, len(q.order))
563 for i, o := range q.order {
564 orders[i] = o.String()
565 }
566 p("Order(%s)", strings.Join(orders, ", "))
567 }
568
569 // Projection
570 if q.project != nil && q.project.Len() > 0 {
571 f := "Project(%s)"
572 if q.distinct {
573 f = "Project[DISTINCT](%s)"
574 }
575 p(f, strings.Join(q.project.ToSlice(), ", "))
576 }
577
578 // Cursors
579 if q.start != nil {
580 p("Start(%q)", q.start.String())
581 }
582 if q.end != nil {
583 p("End(%q)", q.end.String())
584 }
585
586 // Modifiiers
587 if q.limit != nil {
588 p("Limit=%d", *q.limit)
589 }
590 if q.offset != nil {
591 p("Offset=%d", *q.offset)
592 }
593 if q.eventualConsistency {
594 p("EventualConsistency")
595 }
596 if q.keysOnly {
597 p("KeysOnly")
598 }
599
600 ret.WriteRune(')')
601
602 return ret.String()
603 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698