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

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

Issue 1302813003: impl/memory: Implement Queries (Closed) Base URL: https://github.com/luci/gae.git@add_multi_iterator
Patch Set: stringSet everywhere 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
« no previous file with comments | « impl/memory/datastore_query_execution_test.go ('k') | impl/memory/datastore_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 "math" 9 "math"
9 "testing" 10 "testing"
10 11
11 dsS "github.com/luci/gae/service/datastore" 12 dsS "github.com/luci/gae/service/datastore"
13 "github.com/luci/gae/service/datastore/serialize"
14 "github.com/luci/luci-go/common/cmpbin"
15 . "github.com/luci/luci-go/common/testing/assertions"
12 . "github.com/smartystreets/goconvey/convey" 16 . "github.com/smartystreets/goconvey/convey"
13 "golang.org/x/net/context" 17 "golang.org/x/net/context"
14 ) 18 )
15 19
16 const ( 20 const (
17 MaxUint = ^uint(0) 21 MaxUint = ^uint(0)
18 MaxInt = int(MaxUint >> 1) 22 MaxInt = int(MaxUint >> 1)
19 IntIs32Bits = int64(MaxInt) < math.MaxInt64 23 IntIs32Bits = int64(MaxInt) < math.MaxInt64
20 ) 24 )
21 25
22 func TestDatastoreQueries(t *testing.T) { 26 func TestDatastoreQueries(t *testing.T) {
23 Convey("Datastore Query suport", t, func() { 27 Convey("Datastore Query suport", t, func() {
24 c := Use(context.Background()) 28 c := Use(context.Background())
25 ds := dsS.Get(c) 29 ds := dsS.Get(c)
26 So(ds, ShouldNotBeNil) 30 So(ds, ShouldNotBeNil)
27 31
28 Convey("can create good queries", func() { 32 Convey("can create good queries", func() {
29 » » » q := ds.NewQuery("Foo").KeysOnly().Limit(10).Offset(39) 33 » » » q := ds.NewQuery("Foo").Filter("farnsworth >", 20).KeysO nly().Limit(10).Offset(39)
30 » » » q = q.Start(queryCursor("kosmik")).End(queryCursor("krab s")) 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_AIGHyIkAAUc32-A GabMAAA==`)
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)
31 So(q, ShouldNotBeNil) 55 So(q, ShouldNotBeNil)
32 So(q.(*queryImpl).err, ShouldBeNil) 56 So(q.(*queryImpl).err, ShouldBeNil)
33 » » » done, err := q.(*queryImpl).valid("", false) 57 » » » rq, err := q.(*queryImpl).reduce("", false)
34 » » » So(done, ShouldBeFalse) 58 » » » So(rq, ShouldNotBeNil)
35 So(err, ShouldBeNil) 59 So(err, ShouldBeNil)
36 }) 60 })
37 61
38 Convey("ensures orders make sense", func() { 62 Convey("ensures orders make sense", func() {
39 q := ds.NewQuery("Cool") 63 q := ds.NewQuery("Cool")
40 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob ").Order("bob") 64 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob ").Order("bob")
41 65
42 Convey("removes dups and equality orders", func() { 66 Convey("removes dups and equality orders", func() {
43 q = q.Order("wat") 67 q = q.Order("wat")
44 qi := q.(*queryImpl) 68 qi := q.(*queryImpl)
45 » » » » done, err := qi.valid("", false) 69 » » » » So(qi.err, ShouldBeNil)
46 » » » » So(done, ShouldBeFalse) 70 » » » » rq, err := qi.reduce("", false)
47 So(err, ShouldBeNil) 71 So(err, ShouldBeNil)
48 » » » » So(qi.order, ShouldResemble, []dsS.IndexColumn{{ Property: "wat"}}) 72 » » » » So(rq.suffixFormat, ShouldResemble, []dsS.IndexC olumn{
73 » » » » » {Property: "wat"}, {Property: "__key__"} })
49 }) 74 })
50 75
51 Convey("if we equality-filter on __key__, that's just si lly", func() { 76 Convey("if we equality-filter on __key__, that's just si lly", func() {
52 » » » » q = q.Order("wat") 77 » » » » q = q.Order("wat").Filter("__key__ =", ds.NewKey ("Foo", "wat", 0, nil))
53 » » » » q := q.Filter("__key__ =", ds.NewKey("Foo", "wat ", 0, nil)) 78 » » » » _, err := q.(*queryImpl).reduce("", false)
54 » » » » _, err := q.(*queryImpl).valid("", false) 79 » » » » So(err, ShouldErrLike, "query equality filter on __key__ is silly")
55 » » » » So(err.Error(), ShouldContainSubstring,
56 » » » » » "query equality filter on __key__ is sil ly")
57 }) 80 })
58 81
59 }) 82 })
60 83
61 Convey("inequalities apply immediately", func() { 84 })
62 // NOTE: this is (maybe?) a slight divergence from reali ty, but it's 85 }
63 // helpful to retain sanity. It's possible that in real- appengine, many 86
64 // inequalities count towards the MaxQueryComponents lim it (100), where 87 type queryTest struct {
65 // in this system we will never have more than 2 (an upp er and lower 88 // name is the name of the test case
66 // bound). 89 name string
67 }) 90
68 91 // q is the input query
69 Convey("can create bad queries", func() { 92 q dsS.Query
70 q := ds.NewQuery("Foo") 93
71 94 // err is the error to expect after prepping the query (error, string or nil)
72 Convey("only one inequality", func() { 95 err interface{}
73 q = q.Order("bob").Order("wat") 96
74 q = q.Filter("bob >", 10).Filter("wat <", 29) 97 // equivalentQuery is another query which ShouldResemble q. This is usef ul to
75 qi := q.(*queryImpl) 98 // see the effects of redundancy pruning on e.g. filters.
76 _, err := qi.valid("", false) 99 equivalentQuery dsS.Query
77 So(err.Error(), ShouldContainSubstring, 100 }
78 "inequality filters on multiple properti es") 101
79 }) 102 type sillyCursor string
80 103
81 Convey("bad filter ops", func() { 104 func (s sillyCursor) String() string { return string(s) }
82 q := q.Filter("Bob !", "value") 105
83 So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid operator \"!\"") 106 func curs(pairs ...interface{}) queryCursor {
84 }) 107 if len(pairs)%2 != 0 {
85 Convey("bad filter", func() { 108 panic("curs() takes only even pairs")
86 q := q.Filter("Bob", "value") 109 }
87 So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid filter") 110 pre := &bytes.Buffer{}
88 }) 111 cmpbin.WriteUint(pre, uint64(len(pairs)/2))
89 Convey("bad order", func() { 112 post := serialize.Invertible(&bytes.Buffer{})
90 q := q.Order("+Bob") 113 for i := 0; i < len(pairs); i += 2 {
91 So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid order") 114 k, v := pairs[i].(string), pairs[i+1]
92 }) 115
93 Convey("empty", func() { 116 col := dsS.IndexColumn{Property: k}
94 q := q.Order("") 117
95 So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "empty order") 118 post.SetInvert(false)
96 }) 119 if k[0] == '-' {
97 Convey("OOB limit", func() { 120 post.SetInvert(false)
98 // this is supremely stupid. The SDK uses 'int' which measn we have to 121 col.Property = k[1:]
99 // use it too, but then THEY BOUNDS CHECK IT FOR 32 BITS... *sigh* 122 col.Direction = dsS.DESCENDING
100 if !IntIs32Bits { 123 }
101 q := q.Limit(MaxInt) 124 serialize.WriteIndexColumn(pre, col)
102 So(q.(*queryImpl).err.Error(), ShouldCon tainSubstring, "query limit overflow") 125 serialize.WriteProperty(post, serialize.WithoutContext, prop(v))
126 }
127 return queryCursor(bjoin(pre.Bytes(), post.Bytes()))
128 }
129
130 var queryTests = []queryTest{
131 {"only one inequality",
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
155 {"bad cursors (empty)",
156 nq().Start(queryCursor("")),
157 "invalid cursor", nil},
158
159 {"bad cursors (nil)",
160 nq().Start(queryCursor("")),
161 "invalid cursor", nil},
162
163 {"bad cursors (no key)",
164 nq().End(curs("Foo", 100)),
165 "invalid cursor", nil},
166
167 // 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.
169 {"bad cursors (doesn't include ineq)",
170 nq().Filter("Bob >", 10).Start(
171 curs("Foo", 100, "__key__", key("something", 1)),
172 ),
173 "start cursor is invalid", nil},
174
175 {"bad cursors (doesn't include all orders)",
176 nq().Order("Luci").Order("Charliene").Start(
177 curs("Luci", 100, "__key__", key("something", 1)),
178 ),
179 "start cursor is invalid", nil},
180
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",
190 nq().Order("Luci").End(sillyCursor("I am a banana")),
191 "unknown type", nil},
192
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 <)",
305 nq().Filter("bob >=", 10).Filter("bob <", 10),
306 "done", nil},
307
308 {"overconstrained inequality (> v <)",
309 nq().Filter("bob >", 10).Filter("bob <", 10),
310 "done", nil},
311
312 {"overconstrained inequality (> v <=)",
313 nq().Filter("bob >", 10).Filter("bob <=", 10),
314 "done", nil},
315
316 {"silly inequality (=> v <=)",
317 nq().Filter("bob >=", 10).Filter("bob <=", 10),
318 nil,
319 nil},
320
321 {"Filtering on a reserved property is forbidden",
322 nq().Filter("__special__ >=", 10),
323 "filter on reserved property",
324 nil},
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,
339 nil},
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
353 {"cursors could cause the whole query to be useless",
354 (nq().Filter("Foo >", 3).Filter("Foo <", 10).
355 Start(curs("Foo", 200, "__key__", key("Something", 1))).
356 End(curs("Foo", 1, "__key__", key("Something", 20)))),
357 errQueryDone,
358 nil},
359
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)
103 } 395 }
104 }) 396 })
105 » » » Convey("underflow offset", func() { 397 » » }
106 » » » » q := q.Offset(-29) 398
107 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "negative query offset") 399 » » Convey("non-ancestor queries in a transaction", func() {
108 » » » }) 400 » » » _, err := nq().(*queryImpl).reduce("ns", true)
109 » » » Convey("OOB offset", func() { 401 » » » So(err, ShouldErrLike, "Only ancestor queries")
110 » » » » if !IntIs32Bits {
111 » » » » » q := q.Offset(MaxInt)
112 » » » » » So(q.(*queryImpl).err.Error(), ShouldCon tainSubstring, "query offset overflow")
113 » » » » }
114 » » » })
115 » » » Convey("Bad cursors", func() {
116 » » » » q := q.Start(queryCursor("")).End(queryCursor("" ))
117 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid cursor")
118 » » » })
119 » » » Convey("Bad ancestors", func() {
120 » » » » q := q.Ancestor(ds.NewKey("Goop", "wat", 10, nil ))
121 » » » » So(q, ShouldNotBeNil)
122 » » » » _, err := q.(*queryImpl).valid("", false)
123 » » » » So(err, ShouldEqual, dsS.ErrInvalidKey)
124 » » » })
125 » » » Convey("nil ancestors", func() {
126 » » » » _, err := q.Ancestor(nil).(*queryImpl).valid("", false)
127 » » » » So(err.Error(), ShouldContainSubstring, "nil que ry ancestor")
128 » » » })
129 » » » Convey("Bad key filters", func() {
130 » » » » q := q.Filter("__key__ >", ds.NewKey("Goop", "wa t", 10, nil))
131 » » » » _, err := q.(*queryImpl).valid("", false)
132 » » » » So(err, ShouldEqual, dsS.ErrInvalidKey)
133 » » » })
134 » » » Convey("non-ancestor queries in a transaction", func() {
135 » » » » _, err := q.(*queryImpl).valid("", true)
136 » » » » So(err.Error(), ShouldContainSubstring, "Only an cestor queries")
137 » » » })
138 » » » Convey("absurd numbers of filters are prohibited", func( ) {
139 » » » » q := q.Ancestor(ds.NewKey("thing", "wat", 0, nil ))
140 » » » » for i := 0; i < 100; i++ {
141 » » » » » q = q.Filter("something =", i)
142 » » » » }
143 » » » » _, err := q.(*queryImpl).valid("", false)
144 » » » » So(err.Error(), ShouldContainSubstring, "query i s too large")
145 » » » })
146 » » » Convey("filters for __key__ that aren't keys", func() {
147 » » » » q := q.Filter("__key__ > ", 10)
148 » » » » _, err := q.(*queryImpl).valid("", false)
149 » » » » So(err.Error(), ShouldContainSubstring, "is not a key")
150 » » » })
151 » » » Convey("multiple inequalities", func() {
152 » » » » q := q.Filter("bob > ", 19).Filter("charlie < ", 20)
153 » » » » _, err := q.(*queryImpl).valid("", false)
154 » » » » So(err.Error(), ShouldContainSubstring,
155 » » » » » "inequality filters on multiple properti es")
156 » » » })
157 » » » Convey("bad sort orders", func() {
158 » » » » q := q.Filter("bob > ", 19).Order("-charlie")
159 » » » » _, err := q.(*queryImpl).valid("", false)
160 » » » » So(err.Error(), ShouldContainSubstring, "first s ort order")
161 » » » })
162 » » » Convey("kindless with non-__key__ filters", func() {
163 » » » » q := ds.NewQuery("").Filter("face <", 25.3)
164 » » » » _, err := q.(*queryImpl).valid("", false)
165 » » » » So(err.Error(), ShouldContainSubstring,
166 » » » » » "kindless queries can only filter on __k ey__")
167 » » » })
168 » » » Convey("kindless with non-__key__ orders", func() {
169 » » » » q := ds.NewQuery("").Order("face")
170 » » » » _, err := q.(*queryImpl).valid("", false)
171 » » » » So(err.Error(), ShouldContainSubstring,
172 » » » » » "invalid order for kindless query")
173 » » » })
174 » » » Convey("kindless with decending-__key__ orders", func() {
175 » » » » q := ds.NewQuery("").Order("-__key__")
176 » » » » _, err := q.(*queryImpl).valid("", false)
177 » » » » So(err.Error(), ShouldContainSubstring,
178 » » » » » "invalid order for kindless query")
179 » » » })
180 }) 402 })
181 403
404 Convey("absurd numbers of filters are prohibited", func() {
405 q := nq().Ancestor(key("thing", "wat"))
406 for i := 0; i < 100; i++ {
407 q = q.Filter("something =", i)
408 }
409 //So(q.(*queryImpl).numComponents(), ShouldEqual, 101)
410 _, err := q.(*queryImpl).reduce("ns", false)
411 So(err, ShouldErrLike, "query is too large")
412 })
182 }) 413 })
183 } 414 }
OLDNEW
« no previous file with comments | « impl/memory/datastore_query_execution_test.go ('k') | impl/memory/datastore_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698