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

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: appease errcheck 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 "bytes"
9 "math"
10 "testing" 9 "testing"
11 10
12 » dsS "github.com/luci/gae/service/datastore" 11 » dstore "github.com/luci/gae/service/datastore"
13 "github.com/luci/gae/service/datastore/serialize" 12 "github.com/luci/gae/service/datastore/serialize"
14 "github.com/luci/luci-go/common/cmpbin" 13 "github.com/luci/luci-go/common/cmpbin"
14 "github.com/luci/luci-go/common/stringset"
15 . "github.com/luci/luci-go/common/testing/assertions" 15 . "github.com/luci/luci-go/common/testing/assertions"
16 . "github.com/smartystreets/goconvey/convey" 16 . "github.com/smartystreets/goconvey/convey"
17 "golang.org/x/net/context"
18 ) 17 )
19 18
20 const (
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
102 type sillyCursor string 19 type sillyCursor string
103 20
104 func (s sillyCursor) String() string { return string(s) } 21 func (s sillyCursor) String() string { return string(s) }
105 22
106 func curs(pairs ...interface{}) queryCursor { 23 func curs(pairs ...interface{}) queryCursor {
107 if len(pairs)%2 != 0 { 24 if len(pairs)%2 != 0 {
108 panic("curs() takes only even pairs") 25 panic("curs() takes only even pairs")
109 } 26 }
110 pre := &bytes.Buffer{} 27 pre := &bytes.Buffer{}
111 » cmpbin.WriteUint(pre, uint64(len(pairs)/2)) 28 » if _, err := cmpbin.WriteUint(pre, uint64(len(pairs)/2)); err != nil {
29 » » panic(err)
30 » }
112 post := serialize.Invertible(&bytes.Buffer{}) 31 post := serialize.Invertible(&bytes.Buffer{})
113 for i := 0; i < len(pairs); i += 2 { 32 for i := 0; i < len(pairs); i += 2 {
114 k, v := pairs[i].(string), pairs[i+1] 33 k, v := pairs[i].(string), pairs[i+1]
115 34
116 » » col := dsS.IndexColumn{Property: k} 35 » » col, err := dstore.ParseIndexColumn(k)
36 » » if err != nil {
37 » » » panic(err)
38 » » }
117 39
118 » » post.SetInvert(false) 40 » » post.SetInvert(col.Descending)
119 » » if k[0] == '-' { 41 » » if err := serialize.WriteIndexColumn(pre, col); err != nil {
120 » » » post.SetInvert(false) 42 » » » panic(err)
121 » » » col.Property = k[1:]
122 » » » col.Direction = dsS.DESCENDING
123 } 43 }
124 » » serialize.WriteIndexColumn(pre, col) 44 » » if err := serialize.WriteProperty(post, serialize.WithoutContext , prop(v)); err != nil {
125 » » serialize.WriteProperty(post, serialize.WithoutContext, prop(v)) 45 » » » panic(err)
46 » » }
126 } 47 }
127 return queryCursor(bjoin(pre.Bytes(), post.Bytes())) 48 return queryCursor(bjoin(pre.Bytes(), post.Bytes()))
128 } 49 }
129 50
51 type queryTest struct {
52 // name is the name of the test case
53 name string
54
55 // q is the input query
56 q *dstore.Query
57
58 // err is the error to expect after prepping the query (error, string or nil)
59 err interface{}
60
61 // equivalentQuery is another query which ShouldResemble q. This is usef ul to
62 // see the effects of redundancy pruning on e.g. filters.
63 equivalentQuery *reducedQuery
64 }
65
130 var queryTests = []queryTest{ 66 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)", 67 {"bad cursors (empty)",
156 nq().Start(queryCursor("")), 68 nq().Start(queryCursor("")),
157 "invalid cursor", nil}, 69 "invalid cursor", nil},
158 70
159 {"bad cursors (nil)", 71 {"bad cursors (nil)",
160 nq().Start(queryCursor("")), 72 nq().Start(queryCursor("")),
161 "invalid cursor", nil}, 73 "invalid cursor", nil},
162 74
163 {"bad cursors (no key)", 75 {"bad cursors (no key)",
164 nq().End(curs("Foo", 100)), 76 nq().End(curs("Foo", 100)),
165 "invalid cursor", nil}, 77 "invalid cursor", nil},
166 78
167 // TODO(riannucci): exclude cursors which are out-of-bounds with inequal ity? 79 // 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. 80 // I think right now you could have a query for > 10 with a start cursor of 1.
169 {"bad cursors (doesn't include ineq)", 81 {"bad cursors (doesn't include ineq)",
170 » » nq().Filter("Bob >", 10).Start( 82 » » nq().Gt("Bob", 10).Start(
171 curs("Foo", 100, "__key__", key("something", 1)), 83 curs("Foo", 100, "__key__", key("something", 1)),
172 ), 84 ),
173 "start cursor is invalid", nil}, 85 "start cursor is invalid", nil},
174 86
175 {"bad cursors (doesn't include all orders)", 87 {"bad cursors (doesn't include all orders)",
176 nq().Order("Luci").Order("Charliene").Start( 88 nq().Order("Luci").Order("Charliene").Start(
177 curs("Luci", 100, "__key__", key("something", 1)), 89 curs("Luci", 100, "__key__", key("something", 1)),
178 ), 90 ),
179 "start cursor is invalid", nil}, 91 "start cursor is invalid", nil},
180 92
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", 93 {"cursor bad type",
190 nq().Order("Luci").End(sillyCursor("I am a banana")), 94 nq().Order("Luci").End(sillyCursor("I am a banana")),
191 » » "unknown type", nil}, 95 » » "bad cursor 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 96
304 {"overconstrained inequality (>= v <)", 97 {"overconstrained inequality (>= v <)",
305 » » nq().Filter("bob >=", 10).Filter("bob <", 10), 98 » » nq().Gte("bob", 10).Lt("bob", 10),
306 » » "done", nil}, 99 » » dstore.ErrNullQuery, nil},
307 100
308 {"overconstrained inequality (> v <)", 101 {"overconstrained inequality (> v <)",
309 » » nq().Filter("bob >", 10).Filter("bob <", 10), 102 » » nq().Gt("bob", 10).Lt("bob", 10),
310 » » "done", nil}, 103 » » dstore.ErrNullQuery, nil},
311 104
312 {"overconstrained inequality (> v <=)", 105 {"overconstrained inequality (> v <=)",
313 » » nq().Filter("bob >", 10).Filter("bob <=", 10), 106 » » nq().Gt("bob", 10).Lte("bob", 10),
314 » » "done", nil}, 107 » » dstore.ErrNullQuery, nil},
315 108
316 {"silly inequality (=> v <=)", 109 {"silly inequality (=> v <=)",
317 » » nq().Filter("bob >=", 10).Filter("bob <=", 10), 110 » » nq().Gte("bob", 10).Lte("bob", 10),
318 » » nil, 111 » » nil, 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 112
346 {"cursors get smooshed into the inquality range", 113 {"cursors get smooshed into the inquality range",
347 » » (nq().Filter("Foo >", 3).Filter("Foo <", 10). 114 » » (nq().Gt("Foo", 3).Lt("Foo", 10).
348 Start(curs("Foo", 2, "__key__", key("Something", 1))). 115 Start(curs("Foo", 2, "__key__", key("Something", 1))).
349 End(curs("Foo", 20, "__key__", key("Something", 20)))), 116 End(curs("Foo", 20, "__key__", key("Something", 20)))),
350 nil, 117 nil,
351 » » nq().Filter("Foo >", 3).Filter("Foo <", 10)}, 118 » » &reducedQuery{
119 » » » "ns", "Foo", map[string]stringset.Set{}, []dstore.IndexC olumn{
120 » » » » {Property: "Foo"},
121 » » » » {Property: "__key__"},
122 » » » },
123 » » » increment(serialize.ToBytes(dstore.MkProperty(3))),
124 » » » serialize.ToBytes(dstore.MkProperty(10)),
125 » » » 2,
126 » » }},
352 127
353 {"cursors could cause the whole query to be useless", 128 {"cursors could cause the whole query to be useless",
354 » » (nq().Filter("Foo >", 3).Filter("Foo <", 10). 129 » » (nq().Gt("Foo", 3).Lt("Foo", 10).
355 Start(curs("Foo", 200, "__key__", key("Something", 1))). 130 Start(curs("Foo", 200, "__key__", key("Something", 1))).
356 End(curs("Foo", 1, "__key__", key("Something", 20)))), 131 End(curs("Foo", 1, "__key__", key("Something", 20)))),
357 » » errQueryDone, 132 » » dstore.ErrNullQuery,
358 nil}, 133 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 } 134 }
381 135
382 func TestQueries(t *testing.T) { 136 func TestQueries(t *testing.T) {
383 t.Parallel() 137 t.Parallel()
384 138
385 Convey("queries have tons of condition checking", t, func() { 139 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() { 140 Convey("non-ancestor queries in a transaction", func() {
400 » » » _, err := nq().(*queryImpl).reduce("ns", true) 141 » » » fq, err := nq().Finalize()
401 » » » So(err, ShouldErrLike, "Only ancestor queries") 142 » » » So(err, ShouldErrLike, nil)
143 » » » _, err = reduce(fq, "ns", true)
144 » » » So(err, ShouldErrLike, "must include an Ancestor")
402 }) 145 })
403 146
404 Convey("absurd numbers of filters are prohibited", func() { 147 Convey("absurd numbers of filters are prohibited", func() {
405 q := nq().Ancestor(key("thing", "wat")) 148 q := nq().Ancestor(key("thing", "wat"))
406 for i := 0; i < 100; i++ { 149 for i := 0; i < 100; i++ {
407 » » » » q = q.Filter("something =", i) 150 » » » » q = q.Eq("something", i)
408 } 151 }
409 » » » //So(q.(*queryImpl).numComponents(), ShouldEqual, 101) 152 » » » fq, err := q.Finalize()
410 » » » _, err := q.(*queryImpl).reduce("ns", false) 153 » » » So(err, ShouldErrLike, nil)
154 » » » _, err = reduce(fq, "ns", false)
411 So(err, ShouldErrLike, "query is too large") 155 So(err, ShouldErrLike, "query is too large")
412 }) 156 })
157
158 Convey("bulk check", func() {
159 for _, tc := range queryTests {
160 Convey(tc.name, func() {
161 rq := (*reducedQuery)(nil)
162 fq, err := tc.q.Finalize()
163 if err == nil {
164 err = fq.Valid("s~aid", "ns")
165 if err == nil {
166 rq, err = reduce(fq, "ns ", false)
167 }
168 }
169 So(err, ShouldErrLike, tc.err)
170
171 if tc.equivalentQuery != nil {
172 So(rq, ShouldResemble, tc.equiva lentQuery)
173 }
174 })
175 }
176 })
413 }) 177 })
414 } 178 }
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