Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 */ |
| OLD | NEW |