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

Side by Side Diff: impl/memory/datastore_query_test.go

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: Created 5 years, 3 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
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 */
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698