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 memory | |
6 | |
7 import ( | |
8 "fmt" | |
9 "infra/gae/libs/meta" | |
10 "infra/gae/libs/wrapper" | |
11 "math" | |
12 "testing" | |
13 | |
14 . "github.com/smartystreets/goconvey/convey" | |
15 "golang.org/x/net/context" | |
16 | |
17 "appengine/datastore" | |
18 ) | |
19 | |
20 func TestDatastoreKinder(t *testing.T) { | |
21 t.Parallel() | |
22 | |
23 Convey("Datastore kinds and keys", t, func() { | |
24 c := Use(context.Background()) | |
25 ds := wrapper.GetDS(c) | |
26 So(ds, ShouldNotBeNil) | |
27 | |
28 Convey("implements DSKinder", func() { | |
29 type Foo struct{} | |
30 So(ds.Kind(&Foo{}), ShouldEqual, "Foo") | |
31 | |
32 Convey("which can be tweaked by DSKindSetter", func() { | |
33 ds.SetKindNameResolver(func(interface{}) string
{ return "spam" }) | |
34 So(ds.Kind(&Foo{}), ShouldEqual, "spam") | |
35 | |
36 Convey("and it retains the function so you can s
tack them", func() { | |
37 cur := ds.KindNameResolver() | |
38 ds.SetKindNameResolver(func(o interface{
}) string { return "wat" + cur(o) }) | |
39 So(ds.Kind(&Foo{}), ShouldEqual, "watspa
m") | |
40 }) | |
41 }) | |
42 }) | |
43 | |
44 Convey("implements DSNewKeyer", func() { | |
45 Convey("NewKey", func() { | |
46 key := ds.NewKey("nerd", "stringID", 0, nil) | |
47 So(key, ShouldNotBeNil) | |
48 So(key.Kind(), ShouldEqual, "nerd") | |
49 So(key.StringID(), ShouldEqual, "stringID") | |
50 So(key.IntID(), ShouldEqual, 0) | |
51 So(key.Parent(), ShouldBeNil) | |
52 So(key.AppID(), ShouldEqual, "dev~my~app") | |
53 So(key.Namespace(), ShouldEqual, "") | |
54 So(key.String(), ShouldEqual, "/nerd,stringID") | |
55 So(key.Incomplete(), ShouldBeFalse) | |
56 So(keyValid("", key, userKeyOnly), ShouldBeTrue) | |
57 | |
58 chkey := ds.NewKey("wat", "", 100, key) | |
59 So(chkey, ShouldNotBeNil) | |
60 So(chkey.Kind(), ShouldEqual, "wat") | |
61 So(chkey.StringID(), ShouldEqual, "") | |
62 So(chkey.IntID(), ShouldEqual, 100) | |
63 So(chkey.Parent(), ShouldEqual, key) | |
64 So(chkey.AppID(), ShouldEqual, "dev~my~app") | |
65 So(chkey.Namespace(), ShouldEqual, "") | |
66 So(chkey.String(), ShouldEqual, "/nerd,stringID/
wat,100") | |
67 So(key.Incomplete(), ShouldBeFalse) | |
68 So(keyValid("", chkey, userKeyOnly), ShouldBeTru
e) | |
69 | |
70 incompl := ds.NewKey("sup", "", 0, key) | |
71 So(incompl, ShouldNotBeNil) | |
72 So(incompl.Incomplete(), ShouldBeTrue) | |
73 So(keyValid("", incompl, userKeyOnly), ShouldBeT
rue) | |
74 So(incompl.String(), ShouldEqual, "/nerd,stringI
D/sup,0") | |
75 | |
76 bad := ds.NewKey("nooo", "", 10, incompl) | |
77 So(bad, ShouldNotBeNil) | |
78 So(bad.Incomplete(), ShouldBeFalse) | |
79 So(keyValid("", bad, userKeyOnly), ShouldBeFalse
) | |
80 So(bad.String(), ShouldEqual, "/nerd,stringID/su
p,0/nooo,10") | |
81 | |
82 So(rootKey(bad), ShouldEqual, key) | |
83 | |
84 Convey("other key validation", func() { | |
85 So(keyValid("", nil, userKeyOnly), Shoul
dBeFalse) | |
86 | |
87 key := ds.NewKey("", "", 0, nil) | |
88 So(key, ShouldNotBeNil) | |
89 | |
90 So(keyValid("", key, userKeyOnly), Shoul
dBeFalse) | |
91 | |
92 key = ds.NewKey("noop", "power level", 9
000, nil) | |
93 So(key, ShouldNotBeNil) | |
94 | |
95 So(keyValid("", key, userKeyOnly), Shoul
dBeFalse) | |
96 }) | |
97 }) | |
98 | |
99 Convey("NewKeyObj", func() { | |
100 type Foo struct { | |
101 _knd string `goon:"kind,coool"
` | |
102 ID int64 `goon:"id"` | |
103 Parent *datastore.Key `goon:"parent"` | |
104 } | |
105 f := &Foo{ID: 100} | |
106 k := ds.NewKeyObj(f) | |
107 So(k.String(), ShouldEqual, "/coool,100") | |
108 | |
109 f.Parent = k | |
110 f._knd = "weevils" | |
111 f.ID = 19 | |
112 k = ds.NewKeyObj(f) | |
113 So(k.String(), ShouldEqual, "/coool,100/weevils,
19") | |
114 | |
115 Convey("panics when you do a dumb thing", func()
{ | |
116 type Foo struct { | |
117 ID []byte `goon:"id"` | |
118 } | |
119 So(func() { ds.NewKeyObj(&Foo{}) }, Shou
ldPanic) | |
120 }) | |
121 }) | |
122 | |
123 Convey("NewKeyObjError", func() { | |
124 type Foo struct { | |
125 ID []byte `goon:"id"` | |
126 } | |
127 _, err := ds.NewKeyObjError(&Foo{}) | |
128 So(err.Error(), ShouldContainSubstring, "must be
int64 or string") | |
129 }) | |
130 }) | |
131 | |
132 }) | |
133 } | |
134 | |
135 func TestDatastoreSingleReadWriter(t *testing.T) { | |
136 t.Parallel() | |
137 | |
138 Convey("Datastore single reads and writes", t, func() { | |
139 c := Use(context.Background()) | |
140 ds := wrapper.GetDS(c) | |
141 So(ds, ShouldNotBeNil) | |
142 | |
143 Convey("implements DSSingleReadWriter", func() { | |
144 type Foo struct { | |
145 ID int64 `goon:"id" datastore:"-"` | |
146 Parent *datastore.Key `goon:"parent" datastore:"
-"` | |
147 Val int | |
148 } | |
149 | |
150 Convey("invalid keys break", func() { | |
151 k := ds.NewKeyObj(&Foo{}) | |
152 f := &Foo{Parent: k} | |
153 So(ds.Get(f), ShouldEqual, datastore.ErrInvalidK
ey) | |
154 | |
155 _, err := ds.Put(f) | |
156 So(err, ShouldEqual, datastore.ErrInvalidKey) | |
157 }) | |
158 | |
159 Convey("getting objects that DNE is an error", func() { | |
160 So(ds.Get(&Foo{ID: 1}), ShouldEqual, datastore.E
rrNoSuchEntity) | |
161 }) | |
162 | |
163 Convey("Can Put stuff", func() { | |
164 // with an incomplete key! | |
165 f := &Foo{Val: 10} | |
166 k, err := ds.Put(f) | |
167 So(err, ShouldBeNil) | |
168 So(k.String(), ShouldEqual, "/Foo,1") | |
169 So(ds.NewKeyObj(f), ShouldResemble, k) | |
170 | |
171 Convey("and Get it back", func() { | |
172 newFoo := &Foo{ID: 1} | |
173 err := ds.Get(newFoo) | |
174 So(err, ShouldBeNil) | |
175 So(newFoo, ShouldResemble, f) | |
176 | |
177 Convey("and we can Delete it", func() { | |
178 err := ds.Delete(ds.NewKey("Foo"
, "", 1, nil)) | |
179 So(err, ShouldBeNil) | |
180 | |
181 err = ds.Get(newFoo) | |
182 So(err, ShouldEqual, datastore.E
rrNoSuchEntity) | |
183 }) | |
184 }) | |
185 Convey("Deleteing with a bogus key is bad", func
() { | |
186 err := ds.Delete(ds.NewKey("Foo", "wat",
100, nil)) | |
187 So(err, ShouldEqual, datastore.ErrInvali
dKey) | |
188 }) | |
189 Convey("Deleteing a DNE entity is fine", func()
{ | |
190 err := ds.Delete(ds.NewKey("Foo", "wat",
0, nil)) | |
191 So(err, ShouldBeNil) | |
192 }) | |
193 | |
194 Convey("serialization breaks in the normal ways"
, func() { | |
195 type BadFoo struct { | |
196 _kind string `goon:"kind,Foo"` | |
197 ID int64 `goon:"id" datastor
e:"-"` | |
198 Val uint8 | |
199 } | |
200 _, err := ds.Put(&BadFoo{}) | |
201 So(err.Error(), ShouldContainSubstring, | |
202 "unsupported struct field type:
uint8") | |
203 | |
204 err = ds.Get(&BadFoo{ID: 1}) | |
205 So(err.Error(), ShouldContainSubstring, | |
206 "type mismatch: int versus uint8
") | |
207 }) | |
208 | |
209 Convey("check that metadata works", func() { | |
210 val, _ := meta.GetEntityGroupVersion(c,
k) | |
211 So(val, ShouldEqual, 1) | |
212 | |
213 for i := 0; i < 10; i++ { | |
214 _, err = ds.Put(&Foo{Val: 10, Pa
rent: k}) | |
215 So(err, ShouldBeNil) | |
216 } | |
217 val, _ = meta.GetEntityGroupVersion(c, k
) | |
218 So(val, ShouldEqual, 11) | |
219 | |
220 Convey("ensure that group versions persi
st across deletes", func() { | |
221 So(ds.Delete(k), ShouldBeNil) | |
222 for i := int64(1); i < 11; i++ { | |
223 So(ds.Delete(ds.NewKey("
Foo", "", i, k)), ShouldBeNil) | |
224 } | |
225 // TODO(riannucci): replace with
a Count query instead of this cast | |
226 ents := ds.(*dsImpl).data.store.
GetCollection("ents:") | |
227 num, _ := ents.GetTotals() | |
228 // /__entity_root_ids__,Foo | |
229 // /Foo,1/__entity_group__,1 | |
230 // /Foo,1/__entity_group_ids__,1 | |
231 So(num, ShouldEqual, 3) | |
232 | |
233 version, err := curVersion(ents,
groupMetaKey(k)) | |
234 So(err, ShouldBeNil) | |
235 So(version, ShouldEqual, 22) | |
236 | |
237 k, err := ds.Put(f) | |
238 So(err, ShouldBeNil) | |
239 val, _ := meta.GetEntityGroupVer
sion(c, k) | |
240 So(val, ShouldEqual, 23) | |
241 }) | |
242 }) | |
243 }) | |
244 }) | |
245 | |
246 Convey("implements DSTransactioner", func() { | |
247 type Foo struct { | |
248 ID int64 `goon:"id" datastore:"-"` | |
249 Parent *datastore.Key `goon:"parent" datastore:"
-"` | |
250 Val int | |
251 } | |
252 Convey("Put", func() { | |
253 f := &Foo{Val: 10} | |
254 k, err := ds.Put(f) | |
255 So(err, ShouldBeNil) | |
256 So(k.String(), ShouldEqual, "/Foo,1") | |
257 So(ds.NewKeyObj(f), ShouldResemble, k) | |
258 | |
259 Convey("can Put new entity groups", func() { | |
260 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
261 ds := wrapper.GetDS(c) | |
262 So(ds, ShouldNotBeNil) | |
263 | |
264 f1 := &Foo{Val: 100} | |
265 k, err := ds.Put(f1) | |
266 So(err, ShouldBeNil) | |
267 So(k.String(), ShouldEqual, "/Fo
o,2") | |
268 | |
269 f2 := &Foo{Val: 200} | |
270 k, err = ds.Put(f2) | |
271 So(err, ShouldBeNil) | |
272 So(k.String(), ShouldEqual, "/Fo
o,3") | |
273 | |
274 return nil | |
275 }, &datastore.TransactionOptions{XG: tru
e}) | |
276 So(err, ShouldBeNil) | |
277 | |
278 f := &Foo{ID: 2} | |
279 So(ds.Get(f), ShouldBeNil) | |
280 So(f.Val, ShouldEqual, 100) | |
281 | |
282 f = &Foo{ID: 3} | |
283 So(ds.Get(f), ShouldBeNil) | |
284 So(f.Val, ShouldEqual, 200) | |
285 }) | |
286 | |
287 Convey("can Put new entities in a current group"
, func() { | |
288 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
289 ds := wrapper.GetDS(c) | |
290 So(ds, ShouldNotBeNil) | |
291 | |
292 f1 := &Foo{Val: 100, Parent: ds.
NewKeyObj(f)} | |
293 k, err := ds.Put(f1) | |
294 So(err, ShouldBeNil) | |
295 So(k.String(), ShouldEqual, "/Fo
o,1/Foo,1") | |
296 | |
297 f2 := &Foo{Val: 200, Parent: ds.
NewKeyObj(f)} | |
298 k, err = ds.Put(f2) | |
299 So(err, ShouldBeNil) | |
300 So(k.String(), ShouldEqual, "/Fo
o,1/Foo,2") | |
301 | |
302 return nil | |
303 }, nil) | |
304 So(err, ShouldBeNil) | |
305 | |
306 f1 := &Foo{ID: 1, Parent: ds.NewKeyObj(&
Foo{ID: 1})} | |
307 So(ds.Get(f1), ShouldBeNil) | |
308 So(f1.Val, ShouldEqual, 100) | |
309 | |
310 f2 := &Foo{ID: 2, Parent: f1.Parent} | |
311 So(ds.Get(f2), ShouldBeNil) | |
312 So(f2.Val, ShouldEqual, 200) | |
313 }) | |
314 | |
315 Convey("Deletes work too", func() { | |
316 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
317 ds := wrapper.GetDS(c) | |
318 So(ds, ShouldNotBeNil) | |
319 So(ds.Delete(ds.NewKeyObj(f)), S
houldBeNil) | |
320 return nil | |
321 }, nil) | |
322 So(err, ShouldBeNil) | |
323 So(ds.Get(f), ShouldEqual, datastore.Err
NoSuchEntity) | |
324 }) | |
325 | |
326 Convey("A Get counts against your group count",
func() { | |
327 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
328 ds := wrapper.GetDS(c) | |
329 f := &Foo{ID: 20} | |
330 So(ds.Get(f), ShouldEqual, datas
tore.ErrNoSuchEntity) | |
331 | |
332 f.ID = 1 | |
333 So(ds.Get(f).Error(), ShouldCont
ainSubstring, "cross-group") | |
334 return nil | |
335 }, nil) | |
336 So(err, ShouldBeNil) | |
337 }) | |
338 | |
339 Convey("Get takes a snapshot", func() { | |
340 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
341 txnDS := wrapper.GetDS(c) | |
342 So(txnDS, ShouldNotBeNil) | |
343 | |
344 f := &Foo{ID: 1} | |
345 So(txnDS.Get(f), ShouldBeNil) | |
346 So(f.Val, ShouldEqual, 10) | |
347 | |
348 // Don't ever do this in a real
program unless you want to guarantee | |
349 // a failed transaction :) | |
350 f.Val = 11 | |
351 _, err := ds.Put(f) | |
352 So(err, ShouldBeNil) | |
353 | |
354 So(txnDS.Get(f), ShouldBeNil) | |
355 So(f.Val, ShouldEqual, 10) | |
356 | |
357 return nil | |
358 }, nil) | |
359 So(err, ShouldBeNil) | |
360 | |
361 f := &Foo{ID: 1} | |
362 So(ds.Get(f), ShouldBeNil) | |
363 So(f.Val, ShouldEqual, 11) | |
364 | |
365 }) | |
366 | |
367 Convey("and snapshots are consistent even after
Puts", func() { | |
368 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
369 txnDS := wrapper.GetDS(c) | |
370 So(txnDS, ShouldNotBeNil) | |
371 | |
372 f := &Foo{ID: 1} | |
373 So(txnDS.Get(f), ShouldBeNil) | |
374 So(f.Val, ShouldEqual, 10) | |
375 | |
376 // Don't ever do this in a real
program unless you want to guarantee | |
377 // a failed transaction :) | |
378 f.Val = 11 | |
379 _, err := ds.Put(f) | |
380 So(err, ShouldBeNil) | |
381 | |
382 So(txnDS.Get(f), ShouldBeNil) | |
383 So(f.Val, ShouldEqual, 10) | |
384 | |
385 f.Val = 20 | |
386 _, err = txnDS.Put(f) | |
387 So(err, ShouldBeNil) | |
388 | |
389 So(txnDS.Get(f), ShouldBeNil) | |
390 So(f.Val, ShouldEqual, 10) // st
ill gets 10 | |
391 | |
392 return nil | |
393 }, nil) | |
394 So(err.Error(), ShouldContainSubstring,
"concurrent") | |
395 | |
396 f := &Foo{ID: 1} | |
397 So(ds.Get(f), ShouldBeNil) | |
398 So(f.Val, ShouldEqual, 11) | |
399 }) | |
400 | |
401 Convey("Reusing a transaction context is bad new
s", func() { | |
402 txnDS := wrapper.Datastore(nil) | |
403 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
404 txnDS = wrapper.GetDS(c) | |
405 So(txnDS.Get(&Foo{ID: 1}), Shoul
dBeNil) | |
406 return nil | |
407 }, nil) | |
408 So(err, ShouldBeNil) | |
409 So(txnDS.Get(&Foo{ID: 1}).Error(), Shoul
dContainSubstring, "expired") | |
410 }) | |
411 | |
412 Convey("Nested transactions are rejected", func(
) { | |
413 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
414 err := wrapper.GetDS(c).RunInTra
nsaction(func(c context.Context) error { | |
415 panic("noooo") | |
416 }, nil) | |
417 So(err.Error(), ShouldContainSub
string, "nested transactions") | |
418 return nil | |
419 }, nil) | |
420 So(err, ShouldBeNil) | |
421 }) | |
422 | |
423 Convey("Concurrent transactions only accept one
set of changes", func() { | |
424 // Note: I think this implementation is
actually /slightly/ wrong. | |
425 // Accorting to my read of the docs for
appengine, when you open a | |
426 // transaction it actually (essentially)
holds a reference to the | |
427 // entire datastore. Our implementation
takes a snapshot of the | |
428 // entity group as soon as something obs
erves/affects it. | |
429 // | |
430 // That said... I'm not sure if there's
really a semantic difference. | |
431 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
432 txnDS := wrapper.GetDS(c) | |
433 f := &Foo{ID: 1, Val: 21} | |
434 _, err = txnDS.Put(f) | |
435 So(err, ShouldBeNil) | |
436 | |
437 err := ds.RunInTransaction(func(
c context.Context) error { | |
438 txnDS := wrapper.GetDS(c
) | |
439 f := &Foo{ID: 1, Val: 27
} | |
440 _, err := txnDS.Put(f) | |
441 So(err, ShouldBeNil) | |
442 return nil | |
443 }, nil) | |
444 So(err, ShouldBeNil) | |
445 | |
446 return nil | |
447 }, nil) | |
448 So(err.Error(), ShouldContainSubstring,
"concurrent") | |
449 | |
450 f := &Foo{ID: 1} | |
451 So(ds.Get(f), ShouldBeNil) | |
452 So(f.Val, ShouldEqual, 27) | |
453 }) | |
454 | |
455 Convey("XG", func() { | |
456 Convey("Modifying two groups with XG=fal
se is invalid", func() { | |
457 err := ds.RunInTransaction(func(
c context.Context) error { | |
458 ds := wrapper.GetDS(c) | |
459 f := &Foo{ID: 1, Val: 20
0} | |
460 _, err := ds.Put(f) | |
461 So(err, ShouldBeNil) | |
462 | |
463 f.ID = 2 | |
464 _, err = ds.Put(f) | |
465 So(err.Error(), ShouldCo
ntainSubstring, "cross-group") | |
466 return err | |
467 }, nil) | |
468 So(err.Error(), ShouldContainSub
string, "cross-group") | |
469 }) | |
470 | |
471 Convey("Modifying >25 groups with XG=tru
e is invald", func() { | |
472 err := ds.RunInTransaction(func(
c context.Context) error { | |
473 ds := wrapper.GetDS(c) | |
474 for i := int64(1); i < 2
6; i++ { | |
475 f := &Foo{ID: i,
Val: 200} | |
476 _, err := ds.Put
(f) | |
477 So(err, ShouldBe
Nil) | |
478 } | |
479 f := &Foo{ID: 27, Val: 2
00} | |
480 _, err := ds.Put(f) | |
481 So(err.Error(), ShouldCo
ntainSubstring, "too many entity groups") | |
482 return err | |
483 }, &datastore.TransactionOptions
{XG: true}) | |
484 So(err.Error(), ShouldContainSub
string, "too many entity groups") | |
485 }) | |
486 }) | |
487 | |
488 Convey("Errors and panics", func() { | |
489 Convey("returning an error aborts", func
() { | |
490 err := ds.RunInTransaction(func(
c context.Context) error { | |
491 ds := wrapper.GetDS(c) | |
492 f := &Foo{ID: 1, Val: 20
0} | |
493 _, err := ds.Put(f) | |
494 So(err, ShouldBeNil) | |
495 | |
496 return fmt.Errorf("thing
y") | |
497 }, nil) | |
498 So(err.Error(), ShouldEqual, "th
ingy") | |
499 | |
500 f := &Foo{ID: 1} | |
501 So(ds.Get(f), ShouldBeNil) | |
502 So(f.Val, ShouldEqual, 10) | |
503 }) | |
504 | |
505 Convey("panicing aborts", func() { | |
506 So(func() { | |
507 ds.RunInTransaction(func
(c context.Context) error { | |
508 ds := wrapper.Ge
tDS(c) | |
509 f := &Foo{ID: 1,
Val: 200} | |
510 _, err := ds.Put
(f) | |
511 So(err, ShouldBe
Nil) | |
512 panic("wheeeeee"
) | |
513 }, nil) | |
514 }, ShouldPanic) | |
515 | |
516 f := &Foo{ID: 1} | |
517 So(ds.Get(f), ShouldBeNil) | |
518 So(f.Val, ShouldEqual, 10) | |
519 }) | |
520 }) | |
521 }) | |
522 }) | |
523 | |
524 }) | |
525 } | |
526 | |
527 const MaxUint = ^uint(0) | |
528 const MaxInt = int(MaxUint >> 1) | |
529 const IntIs32Bits = int64(MaxInt) < math.MaxInt64 | |
530 | |
531 func TestDatastoreQueryer(t *testing.T) { | |
532 Convey("Datastore Query suport", t, func() { | |
533 c := Use(context.Background()) | |
534 ds := wrapper.GetDS(c) | |
535 So(ds, ShouldNotBeNil) | |
536 | |
537 Convey("can create good queries", func() { | |
538 q := ds.NewQuery("Foo").KeysOnly().Limit(10).Offset(39) | |
539 q = q.Start(queryCursor("kosmik")).End(queryCursor("krab
s")) | |
540 So(q, ShouldNotBeNil) | |
541 So(q.(*queryImpl).err, ShouldBeNil) | |
542 qi := q.(*queryImpl).checkCorrectness("", false) | |
543 So(qi.err, ShouldBeNil) | |
544 }) | |
545 | |
546 Convey("normalize ensures orders make sense", func() { | |
547 q := ds.NewQuery("Cool") | |
548 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob
").Order("bob") | |
549 | |
550 Convey("removes dups and equality orders", func() { | |
551 q = q.Order("wat") | |
552 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
553 So(qi.err, ShouldBeNil) | |
554 So(qi.order, ShouldResemble, []queryOrder{{"wat"
, qASC}}) | |
555 }) | |
556 | |
557 Convey("keeps inequality orders", func() { | |
558 q = q.Order("wat") | |
559 q := q.Filter("bob >", 10).Filter("wat <", 29) | |
560 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
561 So(qi.order, ShouldResemble, []queryOrder{{"bob"
, qASC}, {"wat", qASC}}) | |
562 So(qi.err.Error(), ShouldContainSubstring, "Only
one inequality") | |
563 }) | |
564 | |
565 Convey("if we equality-filter on __key__, order is ditch
ed", func() { | |
566 q = q.Order("wat") | |
567 q := q.Filter("__key__ =", ds.NewKey("Foo", "wat
", 0, nil)) | |
568 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
569 So(qi.order, ShouldResemble, []queryOrder(nil)) | |
570 So(qi.err, ShouldBeNil) | |
571 }) | |
572 | |
573 Convey("if we order by key and something else, key domin
ates", func() { | |
574 q := q.Order("__key__").Order("wat") | |
575 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
576 So(qi.order, ShouldResemble, []queryOrder{{"__ke
y__", qASC}}) | |
577 So(qi.err, ShouldBeNil) | |
578 }) | |
579 }) | |
580 | |
581 Convey("can create bad queries", func() { | |
582 q := ds.NewQuery("Foo") | |
583 | |
584 Convey("bad filter ops", func() { | |
585 q := q.Filter("Bob !", "value") | |
586 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid operator \"!\"") | |
587 }) | |
588 Convey("bad filter", func() { | |
589 q := q.Filter("Bob", "value") | |
590 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid filter") | |
591 }) | |
592 Convey("bad order", func() { | |
593 q := q.Order("+Bob") | |
594 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid order") | |
595 }) | |
596 Convey("empty", func() { | |
597 q := q.Order("") | |
598 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "empty order") | |
599 }) | |
600 Convey("OOB limit", func() { | |
601 // this is supremely stupid. The SDK uses 'int'
which measn we have to | |
602 // use it too, but then THEY BOUNDS CHECK IT FOR
32 BITS... *sigh* | |
603 if !IntIs32Bits { | |
604 q := q.Limit(MaxInt) | |
605 So(q.(*queryImpl).err.Error(), ShouldCon
tainSubstring, "query limit overflow") | |
606 } | |
607 }) | |
608 Convey("underflow offset", func() { | |
609 q := q.Offset(-29) | |
610 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "negative query offset") | |
611 }) | |
612 Convey("OOB offset", func() { | |
613 if !IntIs32Bits { | |
614 q := q.Offset(MaxInt) | |
615 So(q.(*queryImpl).err.Error(), ShouldCon
tainSubstring, "query offset overflow") | |
616 } | |
617 }) | |
618 Convey("Bad cursors", func() { | |
619 q := q.Start(queryCursor("")).End(queryCursor(""
)) | |
620 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid cursor") | |
621 }) | |
622 Convey("Bad ancestors", func() { | |
623 q := q.Ancestor(ds.NewKey("Goop", "wat", 10, nil
)) | |
624 So(q, ShouldNotBeNil) | |
625 qi := q.(*queryImpl).checkCorrectness("", false) | |
626 So(qi.err, ShouldEqual, datastore.ErrInvalidKey) | |
627 }) | |
628 Convey("nil ancestors", func() { | |
629 qi := q.Ancestor(nil).(*queryImpl).checkCorrectn
ess("", false) | |
630 So(qi.err.Error(), ShouldContainSubstring, "nil
query ancestor") | |
631 }) | |
632 Convey("Bad key filters", func() { | |
633 q := q.Filter("__key__ =", ds.NewKey("Goop", "wa
t", 10, nil)) | |
634 qi := q.(*queryImpl).checkCorrectness("", false) | |
635 So(qi.err, ShouldEqual, datastore.ErrInvalidKey) | |
636 }) | |
637 Convey("non-ancestor queries in a transaction", func() { | |
638 qi := q.(*queryImpl).checkCorrectness("", true) | |
639 So(qi.err.Error(), ShouldContainSubstring, "Only
ancestor queries") | |
640 }) | |
641 Convey("absurd numbers of filters are prohibited", func(
) { | |
642 q := q.Ancestor(ds.NewKey("thing", "wat", 0, nil
)) | |
643 for i := 0; i < 100; i++ { | |
644 q = q.Filter("something =", 10) | |
645 } | |
646 qi := q.(*queryImpl).checkCorrectness("", false) | |
647 So(qi.err.Error(), ShouldContainSubstring, "quer
y is too large") | |
648 }) | |
649 Convey("filters for __key__ that aren't keys", func() { | |
650 q := q.Filter("__key__ = ", 10) | |
651 qi := q.(*queryImpl).checkCorrectness("", false) | |
652 So(qi.err.Error(), ShouldContainSubstring, "must
be a Key") | |
653 }) | |
654 Convey("multiple inequalities", func() { | |
655 q := q.Filter("bob > ", 19).Filter("charlie < ",
20) | |
656 qi := q.(*queryImpl).checkCorrectness("", false) | |
657 So(qi.err.Error(), ShouldContainSubstring, "one
inequality filter") | |
658 }) | |
659 Convey("bad sort orders", func() { | |
660 q := q.Filter("bob > ", 19).Order("-charlie") | |
661 qi := q.(*queryImpl).checkCorrectness("", false) | |
662 So(qi.err.Error(), ShouldContainSubstring, "firs
t sort property") | |
663 }) | |
664 Convey("kindless with non-__key__ filters", func() { | |
665 q := ds.NewQuery("").Filter("face <", 25.3) | |
666 qi := q.(*queryImpl).checkCorrectness("", false) | |
667 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for non-__key__") | |
668 }) | |
669 Convey("kindless with non-__key__ orders", func() { | |
670 q := ds.NewQuery("").Order("face") | |
671 qi := q.(*queryImpl).checkCorrectness("", false) | |
672 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for all orders") | |
673 }) | |
674 Convey("kindless with decending-__key__ orders", func()
{ | |
675 q := ds.NewQuery("").Order("-__key__") | |
676 qi := q.(*queryImpl).checkCorrectness("", false) | |
677 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for all orders") | |
678 }) | |
679 }) | |
680 | |
681 }) | |
682 } | |
OLD | NEW |