OLD | NEW |
---|---|
(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 } | |
OLD | NEW |