OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package memory | 5 package memory |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "math" | |
10 "testing" | |
11 | 9 |
12 » dsS "github.com/luci/gae/service/datastore" | 10 » dstore "github.com/luci/gae/service/datastore" |
13 "github.com/luci/gae/service/datastore/serialize" | 11 "github.com/luci/gae/service/datastore/serialize" |
14 "github.com/luci/luci-go/common/cmpbin" | 12 "github.com/luci/luci-go/common/cmpbin" |
15 . "github.com/luci/luci-go/common/testing/assertions" | |
16 . "github.com/smartystreets/goconvey/convey" | |
17 "golang.org/x/net/context" | |
18 ) | 13 ) |
19 | 14 |
iannucci
2015/09/18 04:31:52
I still need to fill this in. You can see some of
iannucci
2015/09/18 22:25:47
Done
| |
20 const ( | 15 // TODO(riannucci): content |
21 » MaxUint = ^uint(0) | |
22 » MaxInt = int(MaxUint >> 1) | |
23 » IntIs32Bits = int64(MaxInt) < math.MaxInt64 | |
24 ) | |
25 | |
26 func TestDatastoreQueries(t *testing.T) { | |
27 » Convey("Datastore Query suport", t, func() { | |
28 » » c := Use(context.Background()) | |
29 » » ds := dsS.Get(c) | |
30 » » So(ds, ShouldNotBeNil) | |
31 | |
32 » » Convey("can create good queries", func() { | |
33 » » » q := ds.NewQuery("Foo").Filter("farnsworth >", 20).KeysO nly().Limit(10).Offset(39) | |
34 | |
35 » » » // normally you can only get cursors from inside of the memory | |
36 » » » // implementation, so this construction is just for test ing. | |
37 » » » start := queryCursor(bjoin( | |
38 » » » » mkNum(2), | |
39 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "far nsworth"}), | |
40 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "__k ey__"}), | |
41 » » » » serialize.ToBytes(prop(200)), | |
42 » » » » serialize.ToBytes(prop(ds.NewKey("Foo", "id", 0, nil))))) | |
43 | |
44 » » » So(start.String(), ShouldEqual, | |
45 » » » » `gYAAZzFdTeeb3d9zOxsAAF-v221Xy32_AIGHyIgAAUc32-A FabMAAA==`) | |
46 | |
47 » » » end := queryCursor(bjoin( | |
48 » » » » mkNum(2), | |
49 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "far nsworth"}), | |
50 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "__k ey__"}), | |
51 » » » » serialize.ToBytes(prop(3000)), | |
52 » » » » serialize.ToBytes(prop(ds.NewKey("Foo", "zeta", 0, nil))))) | |
53 | |
54 » » » q = q.Start(start).End(end) | |
55 » » » So(q, ShouldNotBeNil) | |
56 » » » So(q.(*queryImpl).err, ShouldBeNil) | |
57 » » » rq, err := q.(*queryImpl).reduce("", false) | |
58 » » » So(rq, ShouldNotBeNil) | |
59 » » » So(err, ShouldBeNil) | |
60 » » }) | |
61 | |
62 » » Convey("ensures orders make sense", func() { | |
63 » » » q := ds.NewQuery("Cool") | |
64 » » » q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob ").Order("bob") | |
65 | |
66 » » » Convey("removes dups and equality orders", func() { | |
67 » » » » q = q.Order("wat") | |
68 » » » » qi := q.(*queryImpl) | |
69 » » » » So(qi.err, ShouldBeNil) | |
70 » » » » rq, err := qi.reduce("", false) | |
71 » » » » So(err, ShouldBeNil) | |
72 » » » » So(rq.suffixFormat, ShouldResemble, []dsS.IndexC olumn{ | |
73 » » » » » {Property: "wat"}, {Property: "__key__"} }) | |
74 » » » }) | |
75 | |
76 » » » Convey("if we equality-filter on __key__, that's just si lly", func() { | |
77 » » » » q = q.Order("wat").Filter("__key__ =", ds.NewKey ("Foo", "wat", 0, nil)) | |
78 » » » » _, err := q.(*queryImpl).reduce("", false) | |
79 » » » » So(err, ShouldErrLike, "query equality filter on __key__ is silly") | |
80 » » » }) | |
81 | |
82 » » }) | |
83 | |
84 » }) | |
85 } | |
86 | |
87 type queryTest struct { | |
88 » // name is the name of the test case | |
89 » name string | |
90 | |
91 » // q is the input query | |
92 » q dsS.Query | |
93 | |
94 » // err is the error to expect after prepping the query (error, string or nil) | |
95 » err interface{} | |
96 | |
97 » // equivalentQuery is another query which ShouldResemble q. This is usef ul to | |
98 » // see the effects of redundancy pruning on e.g. filters. | |
99 » equivalentQuery dsS.Query | |
100 } | |
101 | 16 |
102 type sillyCursor string | 17 type sillyCursor string |
103 | 18 |
104 func (s sillyCursor) String() string { return string(s) } | 19 func (s sillyCursor) String() string { return string(s) } |
105 | 20 |
106 func curs(pairs ...interface{}) queryCursor { | 21 func curs(pairs ...interface{}) queryCursor { |
107 if len(pairs)%2 != 0 { | 22 if len(pairs)%2 != 0 { |
108 panic("curs() takes only even pairs") | 23 panic("curs() takes only even pairs") |
109 } | 24 } |
110 pre := &bytes.Buffer{} | 25 pre := &bytes.Buffer{} |
111 cmpbin.WriteUint(pre, uint64(len(pairs)/2)) | 26 cmpbin.WriteUint(pre, uint64(len(pairs)/2)) |
112 post := serialize.Invertible(&bytes.Buffer{}) | 27 post := serialize.Invertible(&bytes.Buffer{}) |
113 for i := 0; i < len(pairs); i += 2 { | 28 for i := 0; i < len(pairs); i += 2 { |
114 k, v := pairs[i].(string), pairs[i+1] | 29 k, v := pairs[i].(string), pairs[i+1] |
115 | 30 |
116 » » col := dsS.IndexColumn{Property: k} | 31 » » col := dstore.IndexColumn{Property: k} |
117 | 32 |
118 post.SetInvert(false) | 33 post.SetInvert(false) |
119 if k[0] == '-' { | 34 if k[0] == '-' { |
120 post.SetInvert(false) | 35 post.SetInvert(false) |
121 col.Property = k[1:] | 36 col.Property = k[1:] |
122 » » » col.Direction = dsS.DESCENDING | 37 » » » col.Direction = dstore.DESCENDING |
123 } | 38 } |
124 serialize.WriteIndexColumn(pre, col) | 39 serialize.WriteIndexColumn(pre, col) |
125 serialize.WriteProperty(post, serialize.WithoutContext, prop(v)) | 40 serialize.WriteProperty(post, serialize.WithoutContext, prop(v)) |
126 } | 41 } |
127 return queryCursor(bjoin(pre.Bytes(), post.Bytes())) | 42 return queryCursor(bjoin(pre.Bytes(), post.Bytes())) |
128 } | 43 } |
129 | 44 |
130 var queryTests = []queryTest{ | 45 /* |
131 » {"only one inequality", | 46 * cases: |
132 » » nq().Order("bob").Order("wat").Filter("bob >", 10).Filter("wat < ", 29), | |
133 » » "inequality filters on multiple properties", nil}, | |
134 | |
135 » {"bad filter ops", | |
136 » » nq().Filter("Bob !", "value"), | |
137 » » "invalid operator \"!\"", nil}, | |
138 | |
139 » {"bad filter", | |
140 » » nq().Filter("Bob", "value"), | |
141 » » "invalid filter", nil}, | |
142 | |
143 » {"bad order", | |
144 » » nq().Order("+Bob"), | |
145 » » "invalid order", nil}, | |
146 | |
147 » {"empty order", | |
148 » » nq().Order(""), | |
149 » » "empty order", nil}, | |
150 | |
151 » {"underflow offset", | |
152 » » nq().Offset(-20), | |
153 » » "negative query offset", nil}, | |
154 | 47 |
155 {"bad cursors (empty)", | 48 {"bad cursors (empty)", |
156 nq().Start(queryCursor("")), | 49 nq().Start(queryCursor("")), |
157 "invalid cursor", nil}, | 50 "invalid cursor", nil}, |
158 | 51 |
159 {"bad cursors (nil)", | 52 {"bad cursors (nil)", |
160 nq().Start(queryCursor("")), | 53 nq().Start(queryCursor("")), |
161 "invalid cursor", nil}, | 54 "invalid cursor", nil}, |
162 | 55 |
163 {"bad cursors (no key)", | 56 {"bad cursors (no key)", |
164 nq().End(curs("Foo", 100)), | 57 nq().End(curs("Foo", 100)), |
165 "invalid cursor", nil}, | 58 "invalid cursor", nil}, |
166 | 59 |
167 // TODO(riannucci): exclude cursors which are out-of-bounds with inequal ity? | 60 // TODO(riannucci): exclude cursors which are out-of-bounds with inequal ity? |
168 // I think right now you could have a query for > 10 with a start cursor of 1. | 61 // I think right now you could have a query for > 10 with a start cursor of 1. |
169 {"bad cursors (doesn't include ineq)", | 62 {"bad cursors (doesn't include ineq)", |
170 nq().Filter("Bob >", 10).Start( | 63 nq().Filter("Bob >", 10).Start( |
171 curs("Foo", 100, "__key__", key("something", 1)), | 64 curs("Foo", 100, "__key__", key("something", 1)), |
172 ), | 65 ), |
173 "start cursor is invalid", nil}, | 66 "start cursor is invalid", nil}, |
174 | 67 |
175 {"bad cursors (doesn't include all orders)", | 68 {"bad cursors (doesn't include all orders)", |
176 nq().Order("Luci").Order("Charliene").Start( | 69 nq().Order("Luci").Order("Charliene").Start( |
177 curs("Luci", 100, "__key__", key("something", 1)), | 70 curs("Luci", 100, "__key__", key("something", 1)), |
178 ), | 71 ), |
179 "start cursor is invalid", nil}, | 72 "start cursor is invalid", nil}, |
180 | 73 |
181 {"cursor set multiple times", | |
182 nq().Order("Luci").End( | |
183 curs("Luci", 100, "__key__", key("something", 1)), | |
184 ).End( | |
185 curs("Luci", 100, "__key__", key("something", 1)), | |
186 ), | |
187 "multiply defined", nil}, | |
188 | |
189 {"cursor bad type", | 74 {"cursor bad type", |
190 nq().Order("Luci").End(sillyCursor("I am a banana")), | 75 nq().Order("Luci").End(sillyCursor("I am a banana")), |
191 "unknown type", nil}, | 76 "unknown type", nil}, |
192 | 77 |
193 {"projecting a keys-only query", | |
194 nq().Project("hello").KeysOnly(), | |
195 "cannot project a keysOnly query", nil}, | |
196 | |
197 {"projecting a keys-only query (reverse)", | |
198 nq().KeysOnly().Project("hello"), | |
199 "cannot project a keysOnly query", nil}, | |
200 | |
201 {"projecting an empty field", | |
202 nq().Project("hello", ""), | |
203 "cannot project on an empty field", nil}, | |
204 | |
205 {"projecting __key__", | |
206 nq().Project("hello", "__key__"), | |
207 "cannot project on __key__", nil}, | |
208 | |
209 {"projecting a duplicate", | |
210 nq().Project("hello", "hello"), | |
211 "cannot project on the same field twice", nil}, | |
212 | |
213 {"projecting a duplicate (style 2)", | |
214 nq().Project("hello").Project("hello"), | |
215 "cannot project on the same field twice", nil}, | |
216 | |
217 {"bad ancestors", | |
218 nq().Ancestor(key("goop", nil)), | |
219 dsS.ErrInvalidKey, nil}, | |
220 | |
221 {"nil ancestors", | |
222 nq().Ancestor(nil), | |
223 "nil query ancestor", nil}, | |
224 | |
225 {"Bad key filters", | |
226 nq().Filter("__key__ >", key("goop", nil)), | |
227 dsS.ErrInvalidKey, nil}, | |
228 | |
229 {"filters for __key__ that aren't keys", | |
230 nq().Filter("__key__ >", 10), | |
231 "is not a key", nil}, | |
232 | |
233 {"multiple inequalities", | |
234 nq().Filter("bob > ", 19).Filter("charlie < ", 20), | |
235 "inequality filters on multiple properties", nil}, | |
236 | |
237 {"inequality must be first sort order", | |
238 nq().Filter("bob > ", 19).Order("-charlie"), | |
239 "first sort order", nil}, | |
240 | |
241 {"inequality must be first sort order (reverse)", | |
242 nq().Order("-charlie").Filter("bob > ", 19), | |
243 "first sort order", nil}, | |
244 | |
245 {"equality filter projected field", | |
246 nq().Project("foo").Filter("foo = ", 10), | |
247 "cannot project", nil}, | |
248 | |
249 {"equality filter projected field (reverse)", | |
250 nq().Filter("foo = ", 10).Project("foo"), | |
251 "cannot project", nil}, | |
252 | |
253 {"kindless with non-__key__ filters", | |
254 nq("").Filter("face <", 25.3), | |
255 "kindless queries can only filter on __key__", nil}, | |
256 | |
257 {"kindless with non-__key__ orders", | |
258 nq("").Order("face"), | |
259 "invalid order for kindless query", nil}, | |
260 | |
261 {"kindless with descending-__key__ order", | |
262 nq("").Order("-__key__"), | |
263 "invalid order for kindless query", nil}, | |
264 | |
265 {"bad namespace", | |
266 nq("something", "sup").Order("__key__"), | |
267 "Namespace mismatched", nil}, | |
268 | |
269 {"distinct non-projection", | |
270 nq().Distinct().Filter("marla >", 1), | |
271 "only makes sense on projection queries", nil}, | |
272 | |
273 {"chained errors return the first", | |
274 nq().Ancestor(nil).Filter("hello", "wurld").Order(""), | |
275 "nil query ancestor", nil}, | |
276 | |
277 {"bad ancestor namespace", | |
278 nq("", "nerd").Ancestor(key("something", "correct")), | |
279 "bad namespace", nil}, | |
280 | |
281 {"multiple ancestors", | |
282 nq().Ancestor(key("something", "correct")).Ancestor(key("somethi ng", "else")), | |
283 "more than one ancestor", nil}, | |
284 | |
285 {"filter with illegal type", | |
286 nq().Filter("something =", complex(1, 2)), | |
287 "bad type complex", nil}, | |
288 | |
289 {"sort orders used for equality are ignored", | |
290 nq().Order("a").Order("b").Order("c").Filter("b =", 2), | |
291 nil, | |
292 nq().Order("a").Order("c").Filter("b =", 2)}, | |
293 | |
294 {"sort orders used for equality are ignored (reversed)", | |
295 nq().Filter("b =", 2).Order("a").Order("b").Order("c"), | |
296 nil, | |
297 nq().Order("a").Order("c").Filter("b =", 2)}, | |
298 | |
299 {"duplicate orders are ignored", | |
300 nq().Order("a").Order("a").Order("a"), | |
301 nil, | |
302 nq().Order("a")}, | |
303 | |
304 {"overconstrained inequality (>= v <)", | 78 {"overconstrained inequality (>= v <)", |
305 nq().Filter("bob >=", 10).Filter("bob <", 10), | 79 nq().Filter("bob >=", 10).Filter("bob <", 10), |
306 "done", nil}, | 80 "done", nil}, |
307 | 81 |
308 {"overconstrained inequality (> v <)", | 82 {"overconstrained inequality (> v <)", |
309 nq().Filter("bob >", 10).Filter("bob <", 10), | 83 nq().Filter("bob >", 10).Filter("bob <", 10), |
310 "done", nil}, | 84 "done", nil}, |
311 | 85 |
312 {"overconstrained inequality (> v <=)", | 86 {"overconstrained inequality (> v <=)", |
313 nq().Filter("bob >", 10).Filter("bob <=", 10), | 87 nq().Filter("bob >", 10).Filter("bob <=", 10), |
314 "done", nil}, | 88 "done", nil}, |
315 | 89 |
316 {"silly inequality (=> v <=)", | 90 {"silly inequality (=> v <=)", |
317 » » nq().Filter("bob >=", 10).Filter("bob <=", 10), | 91 » » nq().Gte("bob", 10).Lte("bob", 10), |
318 nil, | 92 nil, |
319 nil}, | 93 nil}, |
320 | 94 |
321 » {"Filtering on a reserved property is forbidden", | 95 » {"cursors get smooshed into the inquality range", |
322 » » nq().Filter("__special__ >=", 10), | 96 » » (nq().Gt("Foo >", 3).Lt("Foo <", 10). |
323 » » "filter on reserved property", | 97 » » » Start(curs("Foo", 2, "__key__", mkKey("Something", 1))). |
324 » » nil}, | 98 » » » End(curs("Foo", 20, "__key__", mkKey("Something", 20)))) , |
325 | |
326 » {"oob key filters with ancestor (highside)", | |
327 » » nq().Ancestor(key("Hello", 10)).Filter("__key__ <", key("Hello", 9)), | |
328 » » "__key__ inequality", | |
329 » » nil}, | |
330 | |
331 » {"oob key filters with ancestor (lowside)", | |
332 » » nq().Ancestor(key("Hello", 10)).Filter("__key__ >", key("Hello", 11)), | |
333 » » "__key__ inequality", | |
334 » » nil}, | |
335 | |
336 » {"in-bound key filters with ancestor OK", | |
337 » » nq().Ancestor(key("Hello", 10)).Filter("__key__ <", key("Somethi ng", "hi", key("Hello", 10))), | |
338 nil, | 99 nil, |
339 » » nil}, | 100 » » nq().Gt("Foo >", 3).Lt("Foo <", 10)}, |
340 | |
341 » {"projection elements get filled in", | |
342 » » nq().Project("Foo", "Bar").Order("-Bar"), | |
343 » » nil, | |
344 » » nq().Project("Foo", "Bar").Order("-Bar").Order("Foo")}, | |
345 | |
346 » {"cursors get smooshed into the inquality range", | |
347 » » (nq().Filter("Foo >", 3).Filter("Foo <", 10). | |
348 » » » Start(curs("Foo", 2, "__key__", key("Something", 1))). | |
349 » » » End(curs("Foo", 20, "__key__", key("Something", 20)))), | |
350 » » nil, | |
351 » » nq().Filter("Foo >", 3).Filter("Foo <", 10)}, | |
352 | 101 |
353 {"cursors could cause the whole query to be useless", | 102 {"cursors could cause the whole query to be useless", |
354 (nq().Filter("Foo >", 3).Filter("Foo <", 10). | 103 (nq().Filter("Foo >", 3).Filter("Foo <", 10). |
355 Start(curs("Foo", 200, "__key__", key("Something", 1))). | 104 Start(curs("Foo", 200, "__key__", key("Something", 1))). |
356 End(curs("Foo", 1, "__key__", key("Something", 20)))), | 105 End(curs("Foo", 1, "__key__", key("Something", 20)))), |
357 errQueryDone, | 106 errQueryDone, |
358 nil}, | 107 nil}, |
359 | 108 |
360 {"query without anything is fine", | |
361 nq(), | |
362 nil, | |
363 nil}, | |
364 } | |
365 | |
366 func init() { | |
367 // this is supremely stupid. The SDK uses 'int' which measn we have to | |
368 // use it too, but then THEY BOUNDS CHECK IT FOR 32 BITS... *sigh* | |
369 if !IntIs32Bits { | |
370 queryTests = append(queryTests, []queryTest{ | |
371 {"OOB limit (32 bit)", | |
372 nq().Limit(MaxInt), | |
373 "query limit overflow", nil}, | |
374 | |
375 {"OOB offset (32 bit)", | |
376 nq().Offset(MaxInt), | |
377 "query offset overflow", nil}, | |
378 }...) | |
379 } | |
380 } | |
381 | |
382 func TestQueries(t *testing.T) { | |
383 t.Parallel() | |
384 | |
385 Convey("queries have tons of condition checking", t, func() { | |
386 for _, tc := range queryTests { | |
387 Convey(tc.name, func() { | |
388 rq, err := tc.q.(*queryImpl).reduce("ns", false) | |
389 So(err, ShouldErrLike, tc.err) | |
390 | |
391 if tc.equivalentQuery != nil { | |
392 rq2, err := tc.equivalentQuery.(*queryIm pl).reduce("ns", false) | |
393 So(err, ShouldBeNil) | |
394 So(rq, ShouldResemble, rq2) | |
395 } | |
396 }) | |
397 } | |
398 | |
399 Convey("non-ancestor queries in a transaction", func() { | 109 Convey("non-ancestor queries in a transaction", func() { |
400 _, err := nq().(*queryImpl).reduce("ns", true) | 110 _, err := nq().(*queryImpl).reduce("ns", true) |
401 So(err, ShouldErrLike, "Only ancestor queries") | 111 So(err, ShouldErrLike, "Only ancestor queries") |
402 }) | 112 }) |
403 | 113 |
404 Convey("absurd numbers of filters are prohibited", func() { | 114 Convey("absurd numbers of filters are prohibited", func() { |
405 q := nq().Ancestor(key("thing", "wat")) | 115 q := nq().Ancestor(key("thing", "wat")) |
406 for i := 0; i < 100; i++ { | 116 for i := 0; i < 100; i++ { |
407 q = q.Filter("something =", i) | 117 q = q.Filter("something =", i) |
408 } | 118 } |
409 //So(q.(*queryImpl).numComponents(), ShouldEqual, 101) | 119 //So(q.(*queryImpl).numComponents(), ShouldEqual, 101) |
410 _, err := q.(*queryImpl).reduce("ns", false) | 120 _, err := q.(*queryImpl).reduce("ns", false) |
411 So(err, ShouldErrLike, "query is too large") | 121 So(err, ShouldErrLike, "query is too large") |
412 }) | 122 }) |
413 » }) | 123 |
414 } | 124 */ |
OLD | NEW |