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

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

Powered by Google App Engine
This is Rietveld 408576698