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 "math" | |
10 "testing" | |
11 | |
12 dsS "github.com/luci/gae/service/datastore" | |
13 infoS "github.com/luci/gae/service/info" | |
14 . "github.com/smartystreets/goconvey/convey" | |
15 "golang.org/x/net/context" | |
16 ) | |
17 | |
18 func TestDatastoreKinder(t *testing.T) { | |
19 t.Parallel() | |
20 | |
21 Convey("Datastore keys", t, func() { | |
22 c := Use(context.Background()) | |
23 ds := dsS.Get(c) | |
24 So(ds, ShouldNotBeNil) | |
25 | |
26 Convey("implements DSNewKeyer", func() { | |
27 Convey("NewKey", func() { | |
28 key := ds.NewKey("nerd", "stringID", 0, nil) | |
29 So(key, ShouldNotBeNil) | |
30 So(key.Kind(), ShouldEqual, "nerd") | |
31 So(key.StringID(), ShouldEqual, "stringID") | |
32 So(key.IntID(), ShouldEqual, 0) | |
33 So(key.Parent(), ShouldBeNil) | |
34 So(key.AppID(), ShouldEqual, "dev~app") | |
35 So(key.Namespace(), ShouldEqual, "") | |
36 So(key.String(), ShouldEqual, "/nerd,stringID") | |
37 So(dsS.KeyIncomplete(key), ShouldBeFalse) | |
38 So(dsS.KeyValid(key, false, "dev~app", ""), Shou
ldBeTrue) | |
39 }) | |
40 }) | |
41 | |
42 }) | |
43 } | |
44 | |
45 type MetaGroup struct { | |
46 _id int64 `gae:"$id,1"` | |
47 _kind string `gae:"$kind,__entity_group__"` | |
48 Parent dsS.Key `gae:"$parent"` | |
49 | |
50 Version int64 `gae:"__version__"` | |
51 } | |
52 | |
53 func testGetMeta(c context.Context, k dsS.Key) int64 { | |
54 ds := dsS.Get(c) | |
55 mg := &MetaGroup{Parent: dsS.KeyRoot(k)} | |
56 if err := ds.Get(mg); err != nil { | |
57 panic(err) | |
58 } | |
59 return mg.Version | |
60 } | |
61 | |
62 var pls = dsS.GetPLS | |
63 | |
64 type Foo struct { | |
65 Id int64 `gae:"$id"` | |
66 Parent dsS.Key `gae:"$parent"` | |
67 | |
68 Val int | |
69 } | |
70 | |
71 func TestDatastoreSingleReadWriter(t *testing.T) { | |
72 t.Parallel() | |
73 | |
74 Convey("Datastore single reads and writes", t, func() { | |
75 c := Use(context.Background()) | |
76 ds := dsS.Get(c) | |
77 So(ds, ShouldNotBeNil) | |
78 | |
79 Convey("getting objects that DNE is an error", func() { | |
80 So(ds.Get(&Foo{Id: 1}), ShouldEqual, dsS.ErrNoSuchEntity
) | |
81 }) | |
82 | |
83 Convey("bad namespaces fail", func() { | |
84 _, err := infoS.Get(c).Namespace("$$blzyall") | |
85 So(err.Error(), ShouldContainSubstring, "namespace \"$$b
lzyall\" does not match") | |
86 }) | |
87 | |
88 Convey("Can Put stuff", func() { | |
89 // with an incomplete key! | |
90 f := &Foo{Val: 10} | |
91 So(ds.Put(f), ShouldBeNil) | |
92 k := ds.KeyForObj(f) | |
93 So(k.String(), ShouldEqual, "/Foo,1") | |
94 | |
95 Convey("and Get it back", func() { | |
96 newFoo := &Foo{Id: 1} | |
97 So(ds.Get(newFoo), ShouldBeNil) | |
98 So(newFoo, ShouldResemble, f) | |
99 | |
100 Convey("but it's hidden from a different namespa
ce", func() { | |
101 c, err := infoS.Get(c).Namespace("whomba
t") | |
102 So(err, ShouldBeNil) | |
103 ds = dsS.Get(c) | |
104 So(ds.Get(f), ShouldEqual, dsS.ErrNoSuch
Entity) | |
105 }) | |
106 | |
107 Convey("and we can Delete it", func() { | |
108 So(ds.Delete(k), ShouldBeNil) | |
109 So(ds.Get(newFoo), ShouldEqual, dsS.ErrN
oSuchEntity) | |
110 }) | |
111 | |
112 }) | |
113 Convey("Deleteing with a bogus key is bad", func() { | |
114 So(ds.Delete(ds.NewKey("Foo", "wat", 100, nil)),
ShouldEqual, dsS.ErrInvalidKey) | |
115 }) | |
116 Convey("Deleteing a DNE entity is fine", func() { | |
117 So(ds.Delete(ds.NewKey("Foo", "wat", 0, nil)), S
houldBeNil) | |
118 }) | |
119 | |
120 Convey("with multiple puts", func() { | |
121 So(testGetMeta(c, k), ShouldEqual, 1) | |
122 | |
123 foos := make([]Foo, 10) | |
124 for i := range foos { | |
125 foos[i].Val = 10 | |
126 foos[i].Parent = k | |
127 } | |
128 So(ds.PutMulti(foos), ShouldBeNil) | |
129 So(testGetMeta(c, k), ShouldEqual, 11) | |
130 | |
131 keys := make([]dsS.Key, len(foos)) | |
132 for i, f := range foos { | |
133 keys[i] = ds.KeyForObj(&f) | |
134 } | |
135 | |
136 Convey("ensure that group versions persist acros
s deletes", func() { | |
137 So(ds.DeleteMulti(append(keys, k)), Shou
ldBeNil) | |
138 | |
139 // TODO(riannucci): replace with a Count
query instead of this cast | |
140 /* | |
141 ents := ds.(*dsImpl).data.store.
GetCollection("ents:") | |
142 num, _ := ents.GetTotals() | |
143 // /__entity_root_ids__,Foo | |
144 // /Foo,1/__entity_group__,1 | |
145 // /Foo,1/__entity_group_ids__,1 | |
146 So(num, ShouldEqual, 3) | |
147 */ | |
148 | |
149 So(testGetMeta(c, k), ShouldEqual, 22) | |
150 | |
151 So(ds.Put(&Foo{Id: 1}), ShouldBeNil) | |
152 So(testGetMeta(c, k), ShouldEqual, 23) | |
153 }) | |
154 | |
155 Convey("can Get", func() { | |
156 vals := make([]dsS.PropertyMap, len(keys
)) | |
157 for i := range vals { | |
158 vals[i] = dsS.PropertyMap{} | |
159 vals[i].SetMeta("key", keys[i]) | |
160 } | |
161 So(ds.GetMulti(vals), ShouldBeNil) | |
162 | |
163 for i, val := range vals { | |
164 So(val, ShouldResemble, dsS.Prop
ertyMap{ | |
165 "Val": {dsS.MkProperty(
10)}, | |
166 "$key": {dsS.MkPropertyN
I(keys[i])}, | |
167 }) | |
168 } | |
169 }) | |
170 | |
171 }) | |
172 }) | |
173 | |
174 Convey("implements DSTransactioner", func() { | |
175 Convey("Put", func() { | |
176 f := &Foo{Val: 10} | |
177 So(ds.Put(f), ShouldBeNil) | |
178 k := ds.KeyForObj(f) | |
179 So(k.String(), ShouldEqual, "/Foo,1") | |
180 | |
181 Convey("can Put new entity groups", func() { | |
182 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
183 ds := dsS.Get(c) | |
184 | |
185 f := &Foo{Val: 100} | |
186 So(ds.Put(f), ShouldBeNil) | |
187 So(f.Id, ShouldEqual, 2) | |
188 | |
189 f.Id = 0 | |
190 f.Val = 200 | |
191 So(ds.Put(f), ShouldBeNil) | |
192 So(f.Id, ShouldEqual, 3) | |
193 | |
194 return nil | |
195 }, &dsS.TransactionOptions{XG: true}) | |
196 So(err, ShouldBeNil) | |
197 | |
198 f := &Foo{Id: 2} | |
199 So(ds.Get(f), ShouldBeNil) | |
200 So(f.Val, ShouldEqual, 100) | |
201 | |
202 f.Id = 3 | |
203 So(ds.Get(f), ShouldBeNil) | |
204 So(f.Val, ShouldEqual, 200) | |
205 }) | |
206 | |
207 Convey("can Put new entities in a current group"
, func() { | |
208 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
209 ds := dsS.Get(c) | |
210 | |
211 f := &Foo{Val: 100, Parent: k} | |
212 So(ds.Put(f), ShouldBeNil) | |
213 So(ds.KeyForObj(f).String(), Sho
uldEqual, "/Foo,1/Foo,1") | |
214 | |
215 f.Id = 0 | |
216 f.Val = 200 | |
217 So(ds.Put(f), ShouldBeNil) | |
218 So(ds.KeyForObj(f).String(), Sho
uldEqual, "/Foo,1/Foo,2") | |
219 | |
220 return nil | |
221 }, nil) | |
222 So(err, ShouldBeNil) | |
223 | |
224 f := &Foo{Id: 1, Parent: k} | |
225 So(ds.Get(f), ShouldBeNil) | |
226 So(f.Val, ShouldEqual, 100) | |
227 | |
228 f.Id = 2 | |
229 So(ds.Get(f), ShouldBeNil) | |
230 So(f.Val, ShouldEqual, 200) | |
231 }) | |
232 | |
233 Convey("Deletes work too", func() { | |
234 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
235 return dsS.Get(c).Delete(k) | |
236 }, nil) | |
237 So(err, ShouldBeNil) | |
238 So(ds.Get(&Foo{Id: 1}), ShouldEqual, dsS
.ErrNoSuchEntity) | |
239 }) | |
240 | |
241 Convey("A Get counts against your group count",
func() { | |
242 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
243 ds := dsS.Get(c) | |
244 | |
245 pm := dsS.PropertyMap{} | |
246 pm.SetMeta("key", ds.NewKey("Foo
", "", 20, nil)) | |
247 So(ds.Get(pm), ShouldEqual, dsS.
ErrNoSuchEntity) | |
248 | |
249 pm.SetMeta("key", k) | |
250 So(ds.Get(pm).Error(), ShouldCon
tainSubstring, "cross-group") | |
251 return nil | |
252 }, nil) | |
253 So(err, ShouldBeNil) | |
254 }) | |
255 | |
256 Convey("Get takes a snapshot", func() { | |
257 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
258 txnDS := dsS.Get(c) | |
259 | |
260 So(txnDS.Get(f), ShouldBeNil) | |
261 So(f.Val, ShouldEqual, 10) | |
262 | |
263 // Don't ever do this in a real
program unless you want to guarantee | |
264 // a failed transaction :) | |
265 f.Val = 11 | |
266 So(ds.Put(f), ShouldBeNil) | |
267 | |
268 So(txnDS.Get(f), ShouldBeNil) | |
269 So(f.Val, ShouldEqual, 10) | |
270 | |
271 return nil | |
272 }, nil) | |
273 So(err, ShouldBeNil) | |
274 | |
275 f := &Foo{Id: 1} | |
276 So(ds.Get(f), ShouldBeNil) | |
277 So(f.Val, ShouldEqual, 11) | |
278 }) | |
279 | |
280 Convey("and snapshots are consistent even after
Puts", func() { | |
281 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
282 txnDS := dsS.Get(c) | |
283 | |
284 f := &Foo{Id: 1} | |
285 So(txnDS.Get(f), ShouldBeNil) | |
286 So(f.Val, ShouldEqual, 10) | |
287 | |
288 // Don't ever do this in a real
program unless you want to guarantee | |
289 // a failed transaction :) | |
290 f.Val = 11 | |
291 So(ds.Put(f), ShouldBeNil) | |
292 | |
293 So(txnDS.Get(f), ShouldBeNil) | |
294 So(f.Val, ShouldEqual, 10) | |
295 | |
296 f.Val = 20 | |
297 So(txnDS.Put(f), ShouldBeNil) | |
298 | |
299 So(txnDS.Get(f), ShouldBeNil) | |
300 So(f.Val, ShouldEqual, 10) // st
ill gets 10 | |
301 | |
302 return nil | |
303 }, nil) | |
304 So(err.Error(), ShouldContainSubstring,
"concurrent") | |
305 | |
306 f := &Foo{Id: 1} | |
307 So(ds.Get(f), ShouldBeNil) | |
308 So(f.Val, ShouldEqual, 11) | |
309 }) | |
310 | |
311 Convey("Reusing a transaction context is bad new
s", func() { | |
312 txnDS := dsS.Interface(nil) | |
313 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
314 txnDS = dsS.Get(c) | |
315 So(txnDS.Get(f), ShouldBeNil) | |
316 return nil | |
317 }, nil) | |
318 So(err, ShouldBeNil) | |
319 So(txnDS.Get(f).Error(), ShouldContainSu
bstring, "expired") | |
320 }) | |
321 | |
322 Convey("Nested transactions are rejected", func(
) { | |
323 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
324 err := dsS.Get(c).RunInTransacti
on(func(c context.Context) error { | |
325 panic("noooo") | |
326 }, nil) | |
327 So(err.Error(), ShouldContainSub
string, "nested transactions") | |
328 return nil | |
329 }, nil) | |
330 So(err, ShouldBeNil) | |
331 }) | |
332 | |
333 Convey("Concurrent transactions only accept one
set of changes", func() { | |
334 // Note: I think this implementation is
actually /slightly/ wrong. | |
335 // Accorting to my read of the docs for
appengine, when you open a | |
336 // transaction it actually (essentially)
holds a reference to the | |
337 // entire datastore. Our implementation
takes a snapshot of the | |
338 // entity group as soon as something obs
erves/affects it. | |
339 // | |
340 // That said... I'm not sure if there's
really a semantic difference. | |
341 err := ds.RunInTransaction(func(c contex
t.Context) error { | |
342 So(dsS.Get(c).Put(&Foo{Id: 1, Va
l: 21}), ShouldBeNil) | |
343 | |
344 err := ds.RunInTransaction(func(
c context.Context) error { | |
345 So(dsS.Get(c).Put(&Foo{I
d: 1, Val: 27}), ShouldBeNil) | |
346 return nil | |
347 }, nil) | |
348 So(err, ShouldBeNil) | |
349 | |
350 return nil | |
351 }, nil) | |
352 So(err.Error(), ShouldContainSubstring,
"concurrent") | |
353 | |
354 f := &Foo{Id: 1} | |
355 So(ds.Get(f), ShouldBeNil) | |
356 So(f.Val, ShouldEqual, 27) | |
357 }) | |
358 | |
359 Convey("XG", func() { | |
360 Convey("Modifying two groups with XG=fal
se is invalid", func() { | |
361 err := ds.RunInTransaction(func(
c context.Context) error { | |
362 ds := dsS.Get(c) | |
363 f := &Foo{Id: 1, Val: 20
0} | |
364 So(ds.Put(f), ShouldBeNi
l) | |
365 | |
366 f.Id = 2 | |
367 err := ds.Put(f) | |
368 So(err.Error(), ShouldCo
ntainSubstring, "cross-group") | |
369 return err | |
370 }, nil) | |
371 So(err.Error(), ShouldContainSub
string, "cross-group") | |
372 }) | |
373 | |
374 Convey("Modifying >25 groups with XG=tru
e is invald", func() { | |
375 err := ds.RunInTransaction(func(
c context.Context) error { | |
376 ds := dsS.Get(c) | |
377 foos := make([]Foo, 25) | |
378 for i := int64(1); i < 2
6; i++ { | |
379 foos[i-1].Id = i | |
380 foos[i-1].Val =
200 | |
381 } | |
382 So(ds.PutMulti(foos), Sh
ouldBeNil) | |
383 err := ds.Put(&Foo{Id: 2
6}) | |
384 So(err.Error(), ShouldCo
ntainSubstring, "too many entity groups") | |
385 return err | |
386 }, &dsS.TransactionOptions{XG: t
rue}) | |
387 So(err.Error(), ShouldContainSub
string, "too many entity groups") | |
388 }) | |
389 }) | |
390 | |
391 Convey("Errors and panics", func() { | |
392 Convey("returning an error aborts", func
() { | |
393 err := ds.RunInTransaction(func(
c context.Context) error { | |
394 ds := dsS.Get(c) | |
395 So(ds.Put(&Foo{Id: 1, Va
l: 200}), ShouldBeNil) | |
396 return fmt.Errorf("thing
y") | |
397 }, nil) | |
398 So(err.Error(), ShouldEqual, "th
ingy") | |
399 | |
400 f := &Foo{Id: 1} | |
401 So(ds.Get(f), ShouldBeNil) | |
402 So(f.Val, ShouldEqual, 10) | |
403 }) | |
404 | |
405 Convey("panicing aborts", func() { | |
406 So(func() { | |
407 ds.RunInTransaction(func
(c context.Context) error { | |
408 ds := dsS.Get(c) | |
409 So(ds.Put(&Foo{V
al: 200}), ShouldBeNil) | |
410 panic("wheeeeee"
) | |
411 }, nil) | |
412 }, ShouldPanic) | |
413 | |
414 f := &Foo{Id: 1} | |
415 So(ds.Get(f), ShouldBeNil) | |
416 So(f.Val, ShouldEqual, 10) | |
417 }) | |
418 }) | |
419 }) | |
420 }) | |
421 | |
422 }) | |
423 } | |
424 | |
425 const MaxUint = ^uint(0) | |
426 const MaxInt = int(MaxUint >> 1) | |
427 const IntIs32Bits = int64(MaxInt) < math.MaxInt64 | |
428 | |
429 func TestDatastoreQueryer(t *testing.T) { | |
430 Convey("Datastore Query suport", t, func() { | |
431 c := Use(context.Background()) | |
432 ds := dsS.Get(c) | |
433 So(ds, ShouldNotBeNil) | |
434 | |
435 Convey("can create good queries", func() { | |
436 q := ds.NewQuery("Foo").KeysOnly().Limit(10).Offset(39) | |
437 q = q.Start(queryCursor("kosmik")).End(queryCursor("krab
s")) | |
438 So(q, ShouldNotBeNil) | |
439 So(q.(*queryImpl).err, ShouldBeNil) | |
440 qi := q.(*queryImpl).checkCorrectness("", false) | |
441 So(qi.err, ShouldBeNil) | |
442 }) | |
443 | |
444 Convey("normalize ensures orders make sense", func() { | |
445 q := ds.NewQuery("Cool") | |
446 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob
").Order("bob") | |
447 | |
448 Convey("removes dups and equality orders", func() { | |
449 q = q.Order("wat") | |
450 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
451 So(qi.err, ShouldBeNil) | |
452 So(qi.order, ShouldResemble, []queryOrder{{"wat"
, qASC}}) | |
453 }) | |
454 | |
455 Convey("keeps inequality orders", func() { | |
456 q = q.Order("wat") | |
457 q := q.Filter("bob >", 10).Filter("wat <", 29) | |
458 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
459 So(qi.order, ShouldResemble, []queryOrder{{"bob"
, qASC}, {"wat", qASC}}) | |
460 So(qi.err.Error(), ShouldContainSubstring, "Only
one inequality") | |
461 }) | |
462 | |
463 Convey("if we equality-filter on __key__, order is ditch
ed", func() { | |
464 q = q.Order("wat") | |
465 q := q.Filter("__key__ =", ds.NewKey("Foo", "wat
", 0, nil)) | |
466 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
467 So(qi.order, ShouldResemble, []queryOrder(nil)) | |
468 So(qi.err, ShouldBeNil) | |
469 }) | |
470 | |
471 Convey("if we order by key and something else, key domin
ates", func() { | |
472 q := q.Order("__key__").Order("wat") | |
473 qi := q.(*queryImpl).normalize().checkCorrectnes
s("", false) | |
474 So(qi.order, ShouldResemble, []queryOrder{{"__ke
y__", qASC}}) | |
475 So(qi.err, ShouldBeNil) | |
476 }) | |
477 }) | |
478 | |
479 Convey("can create bad queries", func() { | |
480 q := ds.NewQuery("Foo") | |
481 | |
482 Convey("bad filter ops", func() { | |
483 q := q.Filter("Bob !", "value") | |
484 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid operator \"!\"") | |
485 }) | |
486 Convey("bad filter", func() { | |
487 q := q.Filter("Bob", "value") | |
488 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid filter") | |
489 }) | |
490 Convey("bad order", func() { | |
491 q := q.Order("+Bob") | |
492 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid order") | |
493 }) | |
494 Convey("empty", func() { | |
495 q := q.Order("") | |
496 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "empty order") | |
497 }) | |
498 Convey("OOB limit", func() { | |
499 // this is supremely stupid. The SDK uses 'int'
which measn we have to | |
500 // use it too, but then THEY BOUNDS CHECK IT FOR
32 BITS... *sigh* | |
501 if !IntIs32Bits { | |
502 q := q.Limit(MaxInt) | |
503 So(q.(*queryImpl).err.Error(), ShouldCon
tainSubstring, "query limit overflow") | |
504 } | |
505 }) | |
506 Convey("underflow offset", func() { | |
507 q := q.Offset(-29) | |
508 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "negative query offset") | |
509 }) | |
510 Convey("OOB offset", func() { | |
511 if !IntIs32Bits { | |
512 q := q.Offset(MaxInt) | |
513 So(q.(*queryImpl).err.Error(), ShouldCon
tainSubstring, "query offset overflow") | |
514 } | |
515 }) | |
516 Convey("Bad cursors", func() { | |
517 q := q.Start(queryCursor("")).End(queryCursor(""
)) | |
518 So(q.(*queryImpl).err.Error(), ShouldContainSubs
tring, "invalid cursor") | |
519 }) | |
520 Convey("Bad ancestors", func() { | |
521 q := q.Ancestor(ds.NewKey("Goop", "wat", 10, nil
)) | |
522 So(q, ShouldNotBeNil) | |
523 qi := q.(*queryImpl).checkCorrectness("", false) | |
524 So(qi.err, ShouldEqual, dsS.ErrInvalidKey) | |
525 }) | |
526 Convey("nil ancestors", func() { | |
527 qi := q.Ancestor(nil).(*queryImpl).checkCorrectn
ess("", false) | |
528 So(qi.err.Error(), ShouldContainSubstring, "nil
query ancestor") | |
529 }) | |
530 Convey("Bad key filters", func() { | |
531 q := q.Filter("__key__ =", ds.NewKey("Goop", "wa
t", 10, nil)) | |
532 qi := q.(*queryImpl).checkCorrectness("", false) | |
533 So(qi.err, ShouldEqual, dsS.ErrInvalidKey) | |
534 }) | |
535 Convey("non-ancestor queries in a transaction", func() { | |
536 qi := q.(*queryImpl).checkCorrectness("", true) | |
537 So(qi.err.Error(), ShouldContainSubstring, "Only
ancestor queries") | |
538 }) | |
539 Convey("absurd numbers of filters are prohibited", func(
) { | |
540 q := q.Ancestor(ds.NewKey("thing", "wat", 0, nil
)) | |
541 for i := 0; i < 100; i++ { | |
542 q = q.Filter("something =", 10) | |
543 } | |
544 qi := q.(*queryImpl).checkCorrectness("", false) | |
545 So(qi.err.Error(), ShouldContainSubstring, "quer
y is too large") | |
546 }) | |
547 Convey("filters for __key__ that aren't keys", func() { | |
548 q := q.Filter("__key__ = ", 10) | |
549 qi := q.(*queryImpl).checkCorrectness("", false) | |
550 So(qi.err.Error(), ShouldContainSubstring, "must
be a Key") | |
551 }) | |
552 Convey("multiple inequalities", func() { | |
553 q := q.Filter("bob > ", 19).Filter("charlie < ",
20) | |
554 qi := q.(*queryImpl).checkCorrectness("", false) | |
555 So(qi.err.Error(), ShouldContainSubstring, "one
inequality filter") | |
556 }) | |
557 Convey("bad sort orders", func() { | |
558 q := q.Filter("bob > ", 19).Order("-charlie") | |
559 qi := q.(*queryImpl).checkCorrectness("", false) | |
560 So(qi.err.Error(), ShouldContainSubstring, "firs
t sort property") | |
561 }) | |
562 Convey("kindless with non-__key__ filters", func() { | |
563 q := ds.NewQuery("").Filter("face <", 25.3) | |
564 qi := q.(*queryImpl).checkCorrectness("", false) | |
565 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for non-__key__") | |
566 }) | |
567 Convey("kindless with non-__key__ orders", func() { | |
568 q := ds.NewQuery("").Order("face") | |
569 qi := q.(*queryImpl).checkCorrectness("", false) | |
570 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for all orders") | |
571 }) | |
572 Convey("kindless with decending-__key__ orders", func()
{ | |
573 q := ds.NewQuery("").Order("-__key__") | |
574 qi := q.(*queryImpl).checkCorrectness("", false) | |
575 So(qi.err.Error(), ShouldContainSubstring, "kind
is required for all orders") | |
576 }) | |
577 }) | |
578 | |
579 }) | |
580 } | |
OLD | NEW |