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

Side by Side Diff: go/src/infra/gae/libs/wrapper/memory/datastore_query.go

Issue 1160253002: Add initial Query generation, correctness checking and index generation. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: goimports Created 5 years, 6 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 memory
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "math"
12 "strings"
13
14 "appengine/datastore"
15 pb "appengine_internal/datastore"
16
17 "github.com/luci/gkvlite"
18 "github.com/luci/luci-go/common/funnybase"
19
20 "infra/gae/libs/wrapper"
21 )
22
23 type qDirection bool
24
25 const (
26 qASC qDirection = true
27 qDEC = false
28 )
29
30 var builtinQueryPrefix = []byte{0}
31 var complexQueryPrefix = []byte{1}
32
33 type qSort struct {
34 prop string
35 dir qDirection
36 }
37
38 func (qsb qSort) WriteBinary(buf *bytes.Buffer) {
M-A Ruel 2015/05/31 23:03:15 s/qsb/q/ ?
iannucci 2015/05/31 23:31:33 er, yeah... these used to have longer names, but I
39 if qsb.dir == qASC {
40 buf.WriteByte(0)
41 } else {
42 buf.WriteByte(1)
43 }
44 writeString(buf, qsb.prop)
45 }
46
47 func (qsb *qSort) ReadBinary(buf *bytes.Buffer) error {
48 dir, err := buf.ReadByte()
49 if err != nil {
50 return err
51 }
52 qsb.dir = dir == 0
53 qsb.prop, err = readString(buf)
54 return err
55 }
56
57 type qIdx struct {
M-A Ruel 2015/05/31 23:03:15 I think it's excessive shortening, qIndex would be
iannucci 2015/05/31 23:31:33 it also encodes non-composite indexes (turns out t
58 kind string
59 ancestor bool
60 sortby []qSort
61 }
62
63 func (i *qIdx) Builtin() bool {
64 return !i.ancestor && len(i.sortby) <= 1
65 }
66
67 // Valid verifies that this qIdx doesn't have duplicate sortBy fields.
68 func (i *qIdx) Valid() bool {
69 names := map[string]bool{}
70 for _, sb := range i.sortby {
71 if names[sb.prop] {
72 return false
73 }
74 names[sb.prop] = true
75 }
76 return true
77 }
78
79 func (i *qIdx) WriteBinary(buf *bytes.Buffer) {
M-A Ruel 2015/05/31 23:03:15 All these funnybase stuff would have been much bet
iannucci 2015/05/31 23:31:33 bzzt, wrong :D protobufs don't have a stable, sor
80 // TODO(riannucci): do a Grow call here?
81 if i.Builtin() {
82 buf.Write(builtinQueryPrefix)
83 } else {
84 buf.Write(complexQueryPrefix)
85 }
86 writeString(buf, i.kind)
87 if i.ancestor {
88 buf.WriteByte(0)
89 } else {
90 buf.WriteByte(1)
91 }
92 funnybase.WriteUint(buf, uint64(len(i.sortby)))
93 for _, sb := range i.sortby {
94 sb.WriteBinary(buf)
95 }
96 }
97
98 func (i *qIdx) String() string {
99 ret := &bytes.Buffer{}
100 if i.Builtin() {
101 ret.WriteRune('B')
102 } else {
103 ret.WriteRune('C')
104 }
105 ret.WriteRune(':')
106 ret.WriteString(i.kind)
107 if i.ancestor {
108 ret.WriteString("|A")
109 }
110 for _, sb := range i.sortby {
111 ret.WriteRune('/')
112 if sb.dir == qDEC {
113 ret.WriteRune('-')
114 }
115 ret.WriteString(sb.prop)
116 }
117 return ret.String()
118 }
119
120 func (i *qIdx) ReadBinary(buf *bytes.Buffer) error {
121 // discard builtin/complex byte
122 _, err := buf.ReadByte()
123 if err != nil {
124 return err
125 }
126
127 i.kind, err = readString(buf)
128 if err != nil {
129 return err
130 }
131 anc, err := buf.ReadByte()
132 if err != nil {
133 return err
134 }
135 i.ancestor = anc == 1
136
137 numSorts, err := funnybase.ReadUint(buf)
138 if err != nil {
139 return err
140 }
141 i.sortby = make([]qSort, numSorts)
M-A Ruel 2015/05/31 23:03:15 You should protect against large numbers.
iannucci 2015/05/31 23:31:33 yeah, good catch. I do this for bytes. I'll review
142 for idx := range i.sortby {
143 err = (&i.sortby[idx]).ReadBinary(buf)
144 if err != nil {
145 return err
146 }
147 }
148
149 return nil
150 }
151
152 func (i *qIdx) createQueryObjects(pl *propertyList) {
153 // TODO(riannucci): return error if:
154 // No entity can take up more than 2MB in a composite index (I assume this
155 // is the sum of all the composite values in the co mposite index entry)
156
157 panic("createQueryObjects: not implemented")
158
159 /*
160 vmap := make(map[string]datastore.Property, len(*pl))
161 for _, p := range *pl {
162 vmap[p.Name] = p
163 }
164
165 type valueSet struct {
166 propname string
167 values []interface{}
168 }
169 vals := make([]valueSet, len(vmap))
170
171 numCombo := 1
172 for _, sb := range i.sortby {
173 if p, ok := vmap[sb.prop]; !ok || p.NoIndex {
174 return nil
175 } else {
176 if p.Multiple {
177 slice := reflect.ValueOf(p.Value)
178 items := make([]interface{}, slice.Len() )
179 for i := 0; i < slice.Len(); i++ {
180 items[i] = slice.Index(i)
181 }
182 vals = append(vals, valueSet{p.Name, ite ms})
183 numCombo *= len(items)
184 } else {
185 vals = append(vals, valueSet{p.Name, []i nterface{}{p.Value}})
186 }
187 }
188 }
189
190 // generate one queryObject per combination of indices
191 ret := make([]*queryObject, 0, numCombo)
192 keyState := make([]int, len(vals))
193
194 addQO := func() {
195 toApnd := newQueryObject(nil, len(vals)) // TODO(riannuc ci): nil was a key?
196 for valIdx, idx := range keyState {
197 toApnd.data[vals[valIdx].propname] = vals[valIdx ].values[idx]
198 }
199 ret = append(ret, toApnd)
200 }
201
202 movedKey := true
203 for movedKey {
204 addQO()
205
206 movedKey = false
207 for i := len(keyState) - 1; i >= 0; i-- {
208 if keyState[i]+1 == len(vals[i].values) {
209 keyState[i] = 0
210 } else {
211 keyState[i]++
212 movedKey = true
213 break
214 }
215 }
216 }
217
218 return ret
219 */
220 }
221
222 type queryOp int
223
224 const (
225 qInvalid queryOp = iota
226 qEqual
227 qLessThan
228 qLessEq
229 qGreaterEq
230 qGreaterThan
231 )
232
233 func (o queryOp) isEQOp() bool {
234 return o == qEqual
235 }
236
237 func (o queryOp) isINEQOp() bool {
238 return o >= qLessThan && o <= qGreaterThan
239 }
240
241 var queryOpMap = map[string]queryOp{
242 "=": qEqual,
243 "<": qLessThan,
244 "<=": qLessEq,
245 ">=": qGreaterEq,
246 ">": qGreaterThan,
247 }
248
249 type queryFilter struct {
250 field string
251 op queryOp
252 value interface{}
253 }
254
255 func parseFilter(f string, v interface{}) (ret queryFilter, err error) {
256 toks := strings.SplitN(strings.TrimSpace(f), " ", 2)
257 if len(toks) != 2 {
258 err = errors.New("datastore: invalid filter: " + f)
259 } else {
260 op := queryOpMap[toks[1]]
261 if op == qInvalid {
262 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f)
263 } else {
264 ret.field = toks[0]
265 ret.op = op
266 ret.value = v
267 }
268 }
269 return
270 }
271
272 type queryOrder struct {
273 field string
274 direction qDirection
275 }
276
277 type queryCursor string
278
279 func (q queryCursor) String() string { return string(q) }
280 func (q queryCursor) Valid() bool { return q != "" }
281
282 type queryImpl struct {
283 wrapper.DSQuery
284
285 ns string
286
287 kind string
288 ancestor *datastore.Key
289 filter []queryFilter
290 order []queryOrder
291
292 keysOnly bool
293 limit int32
294 offset int32
295
296 start queryCursor
297 end queryCursor
298
299 err error
300 }
301
302 type queryIterImpl struct {
303 idx *queryImpl
304 }
305
306 func (q *queryIterImpl) Cursor() (wrapper.DSCursor, error) {
307 if q.idx.err != nil {
308 return nil, q.idx.err
309 }
310 return nil, nil
311 }
312
313 func (q *queryIterImpl) Next(dst interface{}) (*datastore.Key, error) {
314 if q.idx.err != nil {
315 return nil, q.idx.err
316 }
317 return nil, nil
318 }
319
320 func (q *queryImpl) normalize() (ret *queryImpl) {
321 // ported from GAE SDK datastore_index.py;Normalize()
322 ret = q.clone()
323
324 bs := newMemStore()
325
326 eqProperties := bs.MakePrivateCollection(nil)
327
328 ineqProperties := bs.MakePrivateCollection(nil)
329
330 for _, f := range ret.filter {
331 // if we supported the IN operator, we would check to see if the re were
332 // multiple value operands here, but the go SDK doesn't support this.
333 if f.op.isEQOp() {
334 eqProperties.Set([]byte(f.field), []byte{})
335 } else if f.op.isINEQOp() {
336 ineqProperties.Set([]byte(f.field), []byte{})
337 }
338 }
339
340 ineqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
341 eqProperties.Delete(i.Key)
342 return true
343 })
344
345 removeSet := bs.MakePrivateCollection(nil)
346 eqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
347 removeSet.Set(i.Key, []byte{})
348 return true
349 })
350
351 newOrders := []queryOrder{}
352 for _, o := range ret.order {
353 if removeSet.Get([]byte(o.field)) == nil {
354 removeSet.Set([]byte(o.field), []byte{})
355 newOrders = append(newOrders, o)
356 }
357 }
358 ret.order = newOrders
359
360 // need to fix ret.filters if we ever support the EXISTS operator and/or
361 // projections.
362 //
363 // newFilters = []
364 // for f in ret.filters:
365 // if f.op != qExists:
366 // newFilters = append(newFilters, f)
367 // if !removeSet.Has(f.field):
368 // removeSet.InsertNoReplace(f.field)
369 // newFilters = append(newFilters, f)
370 //
371 // so ret.filters == newFilters becuase none of ret.filters has op == qE xists
372 //
373 // then:
374 //
375 // for prop in ret.project:
376 // if !removeSet.Has(prop):
377 // removeSet.InsertNoReplace(prop)
378 // ... make new EXISTS filters, add them to newFilters ...
379 // ret.filters = newFilters
380 //
381 // However, since we don't support projection queries, this is moot.
382
383 if eqProperties.Get([]byte("__key__")) != nil {
384 ret.order = []queryOrder{}
385 }
386
387 newOrders = []queryOrder{}
388 for _, o := range ret.order {
389 if o.field == "__key__" {
390 newOrders = append(newOrders, o)
391 break
392 }
393 newOrders = append(newOrders, o)
394 }
395 ret.order = newOrders
396
397 return
398 }
399
400 func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) {
401 // ported from GAE SDK datastore_stub_util.py;CheckQuery()
402 ret = q.clone()
403
404 if ns != ret.ns {
405 ret.err = newDSError(pb.Error_BAD_REQUEST,
406 "MADE UP ERROR: Namespace mismatched. Query and Datastor e don't agree "+
407 "on the current namespace")
408 return
409 }
410
411 if ret.err != nil {
412 return
413 }
414
415 // if projection && keys_only:
416 // "projection and keys_only cannot both be set"
417
418 // if projection props match /^__.*__$/:
419 // "projections are not supported for the property: %(prop)s"
420
421 if isTxn && ret.ancestor == nil {
422 ret.err = newDSError(pb.Error_BAD_REQUEST,
423 "Only ancestor queries are allowed inside transactions")
424 return
425 }
426
427 numComponents := len(ret.filter) + len(ret.order)
428 if ret.ancestor != nil {
429 numComponents++
430 }
431 if numComponents > 100 {
432 ret.err = newDSError(pb.Error_BAD_REQUEST,
433 "query is too large. may not have more than "+
434 "100 filters + sort orders ancestor total")
435 }
436
437 // if ret.ancestor.appid() != current appid
438 // "query app is x but ancestor app is x"
439 // if ret.ancestor.namespace() != current namespace
440 // "query namespace is x but ancestor namespace is x"
441
442 // if not all(g in orders for g in group_by)
443 // "items in the group by clause must be specified first in the orderin g"
444
445 ineqPropName := ""
446 for _, f := range ret.filter {
447 if f.field == "__key__" {
448 k, ok := f.value.(*datastore.Key)
449 if !ok {
450 ret.err = newDSError(pb.Error_BAD_REQUEST,
451 "__key__ filter value must be a Key")
452 return
453 }
454 if !keyValid(ret.ns, k, userKeyOnly) {
455 // See the comment in queryImpl.Ancestor; basica lly this check
456 // never happens in the real env because the SDK silently swallows
457 // this condition :/
458 ret.err = datastore.ErrInvalidKey
459 return
460 }
461 // __key__ filter app is X but query app is X
462 // __key__ filter namespace is X but query namespace is X
463 }
464 // if f.op == qEqual and f.field in ret.project_fields
465 // "cannot use projection on a proprety with an equality filte r"
466
467 if f.op.isINEQOp() {
468 if ineqPropName == "" {
469 ineqPropName = f.field
470 } else if f.field != ineqPropName {
471 ret.err = newDSError(pb.Error_BAD_REQUEST,
472 fmt.Sprintf(
473 "Only one inequality filter per query is supported. "+
474 "Encountered both %s and %s", ineqPropName, f.field))
475 return
476 }
477 }
478 }
479
480 // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0
481 // "Inequality filter on X must also be a group by property "+
482 // "when group by properties are set."
483
484 if ineqPropName != "" && len(ret.order) != 0 {
485 if ret.order[0].field != ineqPropName {
486 ret.err = newDSError(pb.Error_BAD_REQUEST,
487 fmt.Sprintf(
488 "The first sort property must be the sam e as the property "+
489 "to which the inequality filter is applied. In your query "+
490 "the first sort property is %s b ut the inequality filter "+
491 "is on %s", ret.order[0].field, ineqPropName))
492 return
493 }
494 }
495
496 if ret.kind == "" {
497 for _, f := range ret.filter {
498 if f.field != "__key__" {
499 ret.err = newDSError(pb.Error_BAD_REQUEST,
500 "kind is required for non-__key__ filter s")
501 return
502 }
503 }
504 for _, o := range ret.order {
505 if o.field != "__key__" || o.direction != qASC {
506 ret.err = newDSError(pb.Error_BAD_REQUEST,
507 "kind is required for all orders except __key__ ascending")
508 return
509 }
510 }
511 }
512 return
513 }
514
515 func (q *queryImpl) calculateIndex() *qIdx {
516 // as a nod to simplicity in this code, we'll require that a single inde x
517 // is able to service the entire query. E.g. no zigzag merge joins or
518 // multiqueries. This will mean that the user will need to rely on
519 // dev_appserver to tell them what indicies they need for real, and for thier
520 // tests they'll need to specify the missing composite indices manually.
521 //
522 // This COULD lead to an exploding indicies problem, but we can fix that when
523 // we get to it.
524
525 //sortOrders := []qSort{}
526
527 return nil
528 }
529
530 func (q *queryImpl) clone() *queryImpl {
531 ret := *q
532 ret.filter = append([]queryFilter(nil), q.filter...)
533 ret.order = append([]queryOrder(nil), q.order...)
534 return &ret
535 }
536
537 func (q *queryImpl) Ancestor(k *datastore.Key) wrapper.DSQuery {
538 q = q.clone()
539 q.ancestor = k
540 if k == nil {
541 // SDK has an explicit nil-check
542 q.err = errors.New("datastore: nil query ancestor")
543 } else if !keyValid(q.ns, k, userKeyOnly) {
544 // technically the SDK implementation does a Weird Thing (tm) if both the
545 // stringID and intID are set on a key; it only serializes the s tringID in
546 // the proto. This means that if you set the Ancestor to an inva lid key,
547 // you'll never actually hear about it. Instead of doing that in sanity, we
548 // just swap to an error here.
549 q.err = datastore.ErrInvalidKey
550 }
551 return q
552 }
553
554 func (q *queryImpl) Filter(fStr string, val interface{}) wrapper.DSQuery {
555 q = q.clone()
556 f, err := parseFilter(fStr, val)
557 if err != nil {
558 q.err = err
559 return q
560 }
561 q.filter = append(q.filter, f)
562 return q
563 }
564
565 func (q *queryImpl) Order(field string) wrapper.DSQuery {
566 q = q.clone()
567 field = strings.TrimSpace(field)
568 o := queryOrder{field, qASC}
569 if strings.HasPrefix(field, "-") {
570 o.direction = qDEC
571 o.field = strings.TrimSpace(field[1:])
572 } else if strings.HasPrefix(field, "+") {
573 q.err = fmt.Errorf("datastore: invalid order: %q", field)
574 return q
575 }
576 if len(o.field) == 0 {
577 q.err = errors.New("datastore: empty order")
578 return q
579 }
580 q.order = append(q.order, o)
581 return q
582 }
583
584 func (q *queryImpl) KeysOnly() wrapper.DSQuery {
585 q = q.clone()
586 q.keysOnly = true
587 return q
588 }
589
590 func (q *queryImpl) Limit(limit int) wrapper.DSQuery {
591 q = q.clone()
592 if limit < math.MinInt32 || limit > math.MaxInt32 {
593 q.err = errors.New("datastore: query limit overflow")
594 return q
595 }
596 q.limit = int32(limit)
597 return q
598 }
599
600 func (q *queryImpl) Offset(offset int) wrapper.DSQuery {
601 q = q.clone()
602 if offset < 0 {
603 q.err = errors.New("datastore: negative query offset")
604 return q
605 }
606 if offset > math.MaxInt32 {
607 q.err = errors.New("datastore: query offset overflow")
608 return q
609 }
610 q.offset = int32(offset)
611 return q
612 }
613
614 func (q *queryImpl) Start(c wrapper.DSCursor) wrapper.DSQuery {
615 q = q.clone()
616 curs := c.(queryCursor)
617 if !curs.Valid() {
618 q.err = errors.New("datastore: invalid cursor")
619 return q
620 }
621 q.start = curs
622 return q
623 }
624
625 func (q *queryImpl) End(c wrapper.DSCursor) wrapper.DSQuery {
626 q = q.clone()
627 curs := c.(queryCursor)
628 if !curs.Valid() {
629 q.err = errors.New("datastore: invalid cursor")
630 return q
631 }
632 q.end = curs
633 return q
634 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698