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

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: Created 5 years, 4 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 "math" 8 "math"
9 "testing" 9 "testing"
10 10
11 dsS "github.com/luci/gae/service/datastore" 11 dsS "github.com/luci/gae/service/datastore"
12 "github.com/luci/gae/service/datastore/serialize"
13 . "github.com/luci/luci-go/common/testing/assertions"
12 . "github.com/smartystreets/goconvey/convey" 14 . "github.com/smartystreets/goconvey/convey"
13 "golang.org/x/net/context" 15 "golang.org/x/net/context"
14 ) 16 )
15 17
16 const ( 18 const (
17 MaxUint = ^uint(0) 19 MaxUint = ^uint(0)
18 MaxInt = int(MaxUint >> 1) 20 MaxInt = int(MaxUint >> 1)
19 IntIs32Bits = int64(MaxInt) < math.MaxInt64 21 IntIs32Bits = int64(MaxInt) < math.MaxInt64
20 ) 22 )
21 23
22 func TestDatastoreQueries(t *testing.T) { 24 func TestDatastoreQueries(t *testing.T) {
23 Convey("Datastore Query suport", t, func() { 25 Convey("Datastore Query suport", t, func() {
24 c := Use(context.Background()) 26 c := Use(context.Background())
25 ds := dsS.Get(c) 27 ds := dsS.Get(c)
26 So(ds, ShouldNotBeNil) 28 So(ds, ShouldNotBeNil)
27 29
28 Convey("can create good queries", func() { 30 Convey("can create good queries", func() {
29 » » » q := ds.NewQuery("Foo").KeysOnly().Limit(10).Offset(39) 31 » » » q := ds.NewQuery("Foo").Filter("farnsworth >", 20).KeysO nly().Limit(10).Offset(39)
30 » » » q = q.Start(queryCursor("kosmik")).End(queryCursor("krab s")) 32
33 » » » // normally you can only get cursors from inside of the memory
34 » » » // implementation, so this construction is just for test ing.
35 » » » start := queryCursor(bjoin(
36 » » » » mkNum(1),
37 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "far nsworth"}),
38 » » » » mkNum(200),
39 » » » » serialize.ToBytes(ds.NewKey("Foo", "id", 0, nil) )))
40
41 » » » So(start.String(), ShouldEqual, `gIAAZzFdTeeb3d9zOxsAh8g AAUc32-AGabMAAA==`)
42
43 » » » end := queryCursor(bjoin(
44 » » » » mkNum(1),
45 » » » » serialize.ToBytes(dsS.IndexColumn{Property: "far nsworth"}),
46 » » » » mkNum(3000),
47 » » » » serialize.ToBytes(ds.NewKey("Foo", "zeta", 0, ni l))))
48
49 » » » q = q.Start(start).End(end)
31 So(q, ShouldNotBeNil) 50 So(q, ShouldNotBeNil)
32 So(q.(*queryImpl).err, ShouldBeNil) 51 So(q.(*queryImpl).err, ShouldBeNil)
33 » » » done, err := q.(*queryImpl).valid("", false) 52 » » » rq, err := q.(*queryImpl).prep("", false)
34 » » » So(done, ShouldBeFalse) 53 » » » So(rq, ShouldNotBeNil)
35 So(err, ShouldBeNil) 54 So(err, ShouldBeNil)
36 }) 55 })
37 56
38 Convey("ensures orders make sense", func() { 57 Convey("ensures orders make sense", func() {
39 q := ds.NewQuery("Cool") 58 q := ds.NewQuery("Cool")
40 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob ").Order("bob") 59 q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob ").Order("bob")
41 60
42 Convey("removes dups and equality orders", func() { 61 Convey("removes dups and equality orders", func() {
43 q = q.Order("wat") 62 q = q.Order("wat")
44 qi := q.(*queryImpl) 63 qi := q.(*queryImpl)
45 » » » » done, err := qi.valid("", false) 64 » » » » rq, err := qi.prep("", false)
46 » » » » So(done, ShouldBeFalse)
47 So(err, ShouldBeNil) 65 So(err, ShouldBeNil)
48 » » » » So(qi.order, ShouldResemble, []dsS.IndexColumn{{ Property: "wat"}}) 66 » » » » So(rq.suffixFormat, ShouldResemble, []dsS.IndexC olumn{{Property: "wat"}})
49 }) 67 })
50 68
51 Convey("if we equality-filter on __key__, that's just si lly", func() { 69 Convey("if we equality-filter on __key__, that's just si lly", func() {
52 » » » » q = q.Order("wat") 70 » » » » q = q.Order("wat").Filter("__key__ =", ds.NewKey ("Foo", "wat", 0, nil))
53 » » » » q := q.Filter("__key__ =", ds.NewKey("Foo", "wat ", 0, nil)) 71 » » » » _, err := q.(*queryImpl).prep("", false)
54 » » » » _, err := q.(*queryImpl).valid("", false) 72 » » » » 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 }) 73 })
58 74
59 }) 75 })
60 76
61 » » Convey("inequalities apply immediately", func() { 77 » })
62 » » » // NOTE: this is (maybe?) a slight divergence from reali ty, but it's 78 }
63 » » » // helpful to retain sanity. It's possible that in real- appengine, many 79
64 » » » // inequalities count towards the MaxQueryComponents lim it (100), where 80 type queryTest struct {
65 » » » // in this system we will never have more than 2 (an upp er and lower 81 » // name is the name of the test case
66 » » » // bound). 82 » name string
67 » » }) 83
68 84 » // q is the input query
69 » » Convey("can create bad queries", func() { 85 » q dsS.Query
70 » » » q := ds.NewQuery("Foo") 86
71 87 » // err is the error to expect after prepping the query (error, string or nil)
72 » » » Convey("only one inequality", func() { 88 » err interface{}
73 » » » » q = q.Order("bob").Order("wat") 89
74 » » » » q = q.Filter("bob >", 10).Filter("wat <", 29) 90 » // equivalentQuery is another query which ShouldResemble q. This is usef ul to
75 » » » » qi := q.(*queryImpl) 91 » // see the effects of redundancy pruning on e.g. filters.
76 » » » » _, err := qi.valid("", false) 92 » equivalentQuery dsS.Query
77 » » » » So(err.Error(), ShouldContainSubstring, 93 }
78 » » » » » "inequality filters on multiple properti es") 94
79 » » » }) 95 var nq dsS.Query = &queryImpl{kind: "Foo", ns: "ns"}
80 96
81 » » » Convey("bad filter ops", func() { 97 type sillyCursor string
82 » » » » q := q.Filter("Bob !", "value") 98
83 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid operator \"!\"") 99 func (s sillyCursor) String() string { return string(s) }
84 » » » }) 100
85 » » » Convey("bad filter", func() { 101 var queryTests = []queryTest{
86 » » » » q := q.Filter("Bob", "value") 102 » {"only one inequality",
87 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid filter") 103 » » nq.Order("bob").Order("wat").Filter("bob >", 10).Filter("wat <", 29),
88 » » » }) 104 » » "inequality filters on multiple properties", nil},
89 » » » Convey("bad order", func() { 105
90 » » » » q := q.Order("+Bob") 106 » {"bad filter ops",
91 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid order") 107 » » nq.Filter("Bob !", "value"),
92 » » » }) 108 » » "invalid operator \"!\"", nil},
93 » » » Convey("empty", func() { 109
94 » » » » q := q.Order("") 110 » {"bad filter",
95 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "empty order") 111 » » nq.Filter("Bob", "value"),
96 » » » }) 112 » » "invalid filter", nil},
97 » » » Convey("OOB limit", func() { 113
98 » » » » // this is supremely stupid. The SDK uses 'int' which measn we have to 114 » {"bad order",
99 » » » » // use it too, but then THEY BOUNDS CHECK IT FOR 32 BITS... *sigh* 115 » » nq.Order("+Bob"),
100 » » » » if !IntIs32Bits { 116 » » "invalid order", nil},
101 » » » » » q := q.Limit(MaxInt) 117
102 » » » » » So(q.(*queryImpl).err.Error(), ShouldCon tainSubstring, "query limit overflow") 118 » {"empty order",
119 » » nq.Order(""),
120 » » "empty order", nil},
121
122 » {"underflow offset",
123 » » nq.Offset(-20),
124 » » "negative query offset", nil},
125
126 » {"bad cursors (empty)",
127 » » nq.Start(queryCursor("")),
128 » » "invalid cursor", nil},
129
130 » {"bad cursors (nil)",
131 » » nq.Start(queryCursor("")),
132 » » "invalid cursor", nil},
133
134 » {"bad cursors (no key)",
135 » » nq.End(queryCursor(serialize.ToBytes(dsS.PropertyMap{
136 » » » "Foo": {prop(100)},
137 » » }))),
138 » » "cursor is invalid", nil},
139
140 » // TODO(riannucci): exclude cursors which are out-of-bounds with inequal ity?
141 » // I think right now you could have a query for > 10 with a start cursor of 1.
142 » {"bad cursors (doesn't include ineq)",
143 » » nq.Filter("Bob >", 10).Start(queryCursor(serialize.ToBytes(dsS.P ropertyMap{
144 » » » "__key__": {prop(key("something", 1))},
145 » » » "Foo": {prop(100)},
146 » » }))),
147 » » "cursor is invalid", nil},
148
149 » {"bad cursors (doesn't include all orders)",
150 » » nq.Order("Luci").Order("Charliene").Start(queryCursor(serialize. ToBytes(dsS.PropertyMap{
151 » » » "__key__": {prop(key("something", 1))},
152 » » » "Luci": {prop(100)},
153 » » }))),
154 » » "cursor is invalid", nil},
155
156 » {"bad cursors (#values != 1)",
157 » » nq.Order("Luci").End(queryCursor(serialize.ToBytes(dsS.PropertyM ap{
158 » » » "__key__": {prop(key("something", 1))},
159 » » » "Luci": {prop(100), prop(101)},
160 » » }))),
161 » » "cursor is invalid", nil},
162
163 » {"cursor set multiple times",
164 » » nq.Order("Luci").End(queryCursor(serialize.ToBytes(dsS.PropertyM ap{
165 » » » "__key__": {prop(key("something", 1))},
166 » » » "Luci": {prop(100), prop(101)},
167 » » }))).End(queryCursor(serialize.ToBytes(dsS.PropertyMap{
168 » » » "__key__": {prop(key("something", 1))},
169 » » » "Luci": {prop(100), prop(101)},
170 » » }))),
171 » » "multiply defined", nil},
172
173 » {"cursor bad type",
174 » » nq.Order("Luci").End(sillyCursor("I am a banana")),
175 » » "unknown type", nil},
176
177 » {"projecting a keys-only query",
178 » » nq.Project("hello").KeysOnly(),
179 » » "cannot project a keysOnly query", nil},
180
181 » {"projecting a keys-only query (reverse)",
182 » » nq.KeysOnly().Project("hello"),
183 » » "cannot project a keysOnly query", nil},
184
185 » {"projecting an empty field",
186 » » nq.Project("hello", ""),
187 » » "cannot project on an empty field", nil},
188
189 » {"projecting __key__",
190 » » nq.Project("hello", "__key__"),
191 » » "cannot project on __key__", nil},
192
193 » {"projecting a duplicate",
194 » » nq.Project("hello", "hello"),
195 » » "cannot project on the same field twice", nil},
196
197 » {"projecting a duplicate (style 2)",
198 » » nq.Project("hello").Project("hello"),
199 » » "cannot project on the same field twice", nil},
200
201 » {"bad ancestors",
202 » » nq.Ancestor(key("goop", nil)),
203 » » dsS.ErrInvalidKey, nil},
204
205 » {"nil ancestors",
206 » » nq.Ancestor(nil),
207 » » "nil query ancestor", nil},
208
209 » {"Bad key filters",
210 » » nq.Filter("__key__ >", key("goop", nil)),
211 » » dsS.ErrInvalidKey, nil},
212
213 » {"filters for __key__ that aren't keys",
214 » » nq.Filter("__key__ >", 10),
215 » » "is not a key", nil},
216
217 » {"multiple inequalities",
218 » » nq.Filter("bob > ", 19).Filter("charlie < ", 20),
219 » » "inequality filters on multiple properties", nil},
220
221 » {"inequality must be first sort order",
222 » » nq.Filter("bob > ", 19).Order("-charlie"),
223 » » "first sort order", nil},
224
225 » {"inequality must be first sort order (reverse)",
226 » » nq.Order("-charlie").Filter("bob > ", 19),
227 » » "first sort order", nil},
228
229 » {"equality filter projected field",
230 » » nq.Project("foo").Filter("foo = ", 10),
231 » » "cannot project", nil},
232
233 » {"equality filter projected field (reverse)",
234 » » nq.Filter("foo = ", 10).Project("foo"),
235 » » "cannot project", nil},
236
237 » {"kindless with non-__key__ filters",
238 » » (&queryImpl{ns: "ns"}).Filter("face <", 25.3),
239 » » "kindless queries can only filter on __key__", nil},
240
241 » {"kindless with non-__key__ orders",
242 » » (&queryImpl{ns: "ns"}).Order("face"),
243 » » "invalid order for kindless query", nil},
244
245 » {"kindless with descending-__key__ order",
246 » » (&queryImpl{ns: "ns"}).Order("-__key__"),
247 » » "invalid order for kindless query", nil},
248
249 » {"bad namespace",
250 » » (&queryImpl{kind: "something", ns: "sup"}).Order("__key__"),
251 » » "Namespace mismatched", nil},
252
253 » {"distinct non-projection",
254 » » nq.Distinct().Filter("marla >", 1),
255 » » "only makes sense on projection queries", nil},
256
257 » {"chained errors return the first",
258 » » nq.Ancestor(nil).Filter("hello", "wurld").Order(""),
259 » » "nil query ancestor", nil},
260
261 » {"bad ancestor namespace",
262 » » (&queryImpl{ns: "nerd"}).Ancestor(key("something", "correct")),
263 » » "bad namespace", nil},
264
265 » {"multiple ancestors",
266 » » nq.Ancestor(key("something", "correct")).Ancestor(key("something ", "else")),
267 » » "more than one ancestor", nil},
268
269 » {"filter with illegal type",
270 » » nq.Filter("something =", complex(1, 2)),
271 » » "bad type complex", nil},
272
273 » {"sort orders used for equality are ignored",
274 » » nq.Order("a").Order("b").Order("c").Filter("b =", 2),
275 » » nil,
276 » » nq.Order("a").Order("c").Filter("b =", 2)},
277
278 » {"sort orders used for equality are ignored (reversed)",
279 » » nq.Filter("b =", 2).Order("a").Order("b").Order("c"),
280 » » nil,
281 » » nq.Order("a").Order("c").Filter("b =", 2)},
282
283 » {"duplicate orders are ignored",
284 » » nq.Order("a").Order("a").Order("a"),
285 » » nil,
286 » » nq.Order("a")},
287
288 » {"overconstrained inequality (>= v <)",
289 » » nq.Filter("bob >=", 10).Filter("bob <", 10),
290 » » "done", nil},
291
292 » {"overconstrained inequality (> v <)",
293 » » nq.Filter("bob >", 10).Filter("bob <", 10),
294 » » "done", nil},
295
296 » {"overconstrained inequality (> v <=)",
297 » » nq.Filter("bob >", 10).Filter("bob <=", 10),
298 » » "done", nil},
299
300 » {"silly inequality (=> v <=)",
301 » » nq.Filter("bob >=", 10).Filter("bob <=", 10),
302 » » nil,
303 » » nil},
304 }
305
306 func init() {
307 » // this is supremely stupid. The SDK uses 'int' which measn we have to
308 » // use it too, but then THEY BOUNDS CHECK IT FOR 32 BITS... *sigh*
309 » if !IntIs32Bits {
310 » » queryTests = append(queryTests, []queryTest{
311 » » » {"OOB limit (32 bit)",
312 » » » » nq.Limit(MaxInt),
313 » » » » "query limit overflow", nil},
314
315 » » » {"OOB offset (32 bit)",
316 » » » » nq.Offset(MaxInt),
317 » » » » "query offset overflow", nil},
318 » » }...)
319 » }
320 }
321
322 func TestBadQueries(t *testing.T) {
323 » t.Parallel()
324
325 » Convey("can create bad queries", t, func() {
326 » » for _, tc := range queryTests {
327 » » » Convey(tc.name, func() {
328 » » » » _, err := tc.q.(*queryImpl).prep("ns", false)
329 » » » » So(err, ShouldErrLike, tc.err)
330
331 » » » » if tc.equivalentQuery != nil {
332 » » » » » So(tc.q, ShouldResemble, tc.equivalentQu ery)
103 } 333 }
104 }) 334 })
105 » » » Convey("underflow offset", func() { 335 » » }
106 » » » » q := q.Offset(-29) 336
107 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "negative query offset") 337 » » Convey("non-ancestor queries in a transaction", func() {
108 » » » }) 338 » » » _, err := nq.(*queryImpl).prep("ns", true)
109 » » » Convey("OOB offset", func() { 339 » » » So(err, ShouldErrLike, "Only ancestor queries")
110 » » » » if !IntIs32Bits { 340 » » })
111 » » » » » q := q.Offset(MaxInt) 341
112 » » » » » So(q.(*queryImpl).err.Error(), ShouldCon tainSubstring, "query offset overflow") 342 » » Convey("absurd numbers of filters are prohibited", func() {
113 » » » » } 343 » » » q := nq.Ancestor(key("thing", "wat"))
114 » » » }) 344 » » » for i := 0; i < 100; i++ {
115 » » » Convey("Bad cursors", func() { 345 » » » » q = q.Filter("something =", i)
116 » » » » q := q.Start(queryCursor("")).End(queryCursor("" )) 346 » » » }
117 » » » » So(q.(*queryImpl).err.Error(), ShouldContainSubs tring, "invalid cursor") 347 » » » //So(q.(*queryImpl).numComponents(), ShouldEqual, 101)
118 » » » }) 348 » » » _, err := q.(*queryImpl).prep("ns", false)
119 » » » Convey("Bad ancestors", func() { 349 » » » So(err, ShouldErrLike, "query is too large")
120 » » » » q := q.Ancestor(ds.NewKey("Goop", "wat", 10, nil )) 350 » » })
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 » » })
181
182 }) 351 })
183 } 352 }
353
354 func TestQueryPreparation(t *testing.T) {
355 t.Parallel()
356
357 Convey("Test Query.prep", t, func() {
358 Convey("ancestor is serialized", func() {
359 rq, err := nq.Ancestor(key("hi", 1)).(*queryImpl).prep(" ns", false)
360 So(err, ShouldBeNil)
361 So(rq.ancestor, ShouldResemble, []byte{0, 1, 0x69, 0x35, 0x40, 1, 0x80, 0x80, 0})
362 })
363
364 Convey("inequality property adjusts start/end", func() {
365
366 })
367 })
368 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698