go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/query_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package datastore 16 17 import ( 18 "math" 19 "testing" 20 21 "go.chromium.org/luci/common/sync/parallel" 22 23 . "github.com/smartystreets/goconvey/convey" 24 . "go.chromium.org/luci/common/testing/assertions" 25 ) 26 27 const ( 28 MaxUint = ^uint(0) 29 MaxInt = int(MaxUint >> 1) 30 IntIs32Bits = int64(MaxInt) < math.MaxInt64 31 ) 32 33 func TestDatastoreQueries(t *testing.T) { 34 Convey("Datastore Query suport", t, func() { 35 Convey("can create good queries", func() { 36 q := NewQuery("Foo").Gt("farnsworth", 20).KeysOnly(true).Limit(10).Offset(39) 37 38 start := fakeCursor(1337) 39 40 end := fakeCursor(24601) 41 42 q = q.Start(start).End(end) 43 So(q, ShouldNotBeNil) 44 fq, err := q.Finalize() 45 So(fq, ShouldNotBeNil) 46 So(err, ShouldBeNil) 47 }) 48 49 Convey("ensures orders make sense", func() { 50 q := NewQuery("Cool") 51 q = q.Eq("cat", 19).Eq("bob", 10).Order("bob", "bob") 52 53 Convey("removes dups and equality orders", func() { 54 q = q.Order("wat") 55 fq, err := q.Finalize() 56 So(err, ShouldBeNil) 57 So(fq.Orders(), ShouldResemble, []IndexColumn{ 58 {Property: "wat"}, {Property: "__key__"}}) 59 }) 60 }) 61 62 }) 63 } 64 65 func TestDatastoreQueriesLess(t *testing.T) { 66 Convey("Datastore Query ordering", t, func() { 67 Convey("Compare kind", func() { 68 q := NewQuery("Foo") 69 q1 := NewQuery("Foo1") 70 71 So(q.Less(q1), ShouldBeTrue) 72 So(q1.Less(q), ShouldBeFalse) 73 74 q2 := NewQuery("Foo") 75 76 So(q.Less(q2), ShouldBeFalse) 77 So(q2.Less(q), ShouldBeFalse) 78 }) 79 Convey("Compare firestore mode", func() { 80 q := NewQuery("Foo") 81 q1 := NewQuery("Foo").FirestoreMode(true) 82 83 So(q.Less(q1), ShouldBeTrue) 84 So(q1.Less(q), ShouldBeFalse) 85 86 q = q.FirestoreMode(true) 87 88 So(q.Less(q1), ShouldBeFalse) 89 So(q1.Less(q), ShouldBeFalse) 90 }) 91 Convey("Compare eventual consistency", func() { 92 q := NewQuery("Foo") 93 q1 := NewQuery("Foo").EventualConsistency(true) 94 95 So(q.Less(q1), ShouldBeTrue) 96 So(q1.Less(q), ShouldBeFalse) 97 98 q = q.EventualConsistency(true) 99 100 So(q.Less(q1), ShouldBeFalse) 101 So(q1.Less(q), ShouldBeFalse) 102 }) 103 Convey("Compare keys only", func() { 104 q := NewQuery("Foo") 105 q1 := NewQuery("Foo").KeysOnly(true) 106 107 So(q.Less(q1), ShouldBeTrue) 108 So(q1.Less(q), ShouldBeFalse) 109 110 q = q.KeysOnly(true) 111 112 So(q.Less(q1), ShouldBeFalse) 113 So(q1.Less(q), ShouldBeFalse) 114 }) 115 Convey("Compare distinct", func() { 116 q := NewQuery("Foo") 117 q1 := NewQuery("Foo").Distinct(true) 118 119 So(q.Less(q1), ShouldBeTrue) 120 So(q1.Less(q), ShouldBeFalse) 121 122 q = q.Distinct(true) 123 124 So(q.Less(q1), ShouldBeFalse) 125 So(q1.Less(q), ShouldBeFalse) 126 }) 127 Convey("Compare limit", func() { 128 q := NewQuery("Foo") 129 q1 := NewQuery("Foo").Limit(20) 130 131 So(q.Less(q1), ShouldBeTrue) 132 So(q1.Less(q), ShouldBeFalse) 133 134 q = q.Limit(20) 135 136 So(q.Less(q1), ShouldBeFalse) 137 So(q1.Less(q), ShouldBeFalse) 138 }) 139 Convey("Compare offset", func() { 140 q := NewQuery("Foo") 141 q1 := NewQuery("Foo").Offset(20) 142 143 So(q.Less(q1), ShouldBeTrue) 144 So(q1.Less(q), ShouldBeFalse) 145 146 q = q.Offset(20) 147 148 So(q.Less(q1), ShouldBeFalse) 149 So(q1.Less(q), ShouldBeFalse) 150 }) 151 Convey("Compare order", func() { 152 q := NewQuery("Foo") 153 q1 := NewQuery("Foo").Order("conrad") 154 155 So(q.Less(q1), ShouldBeTrue) 156 So(q1.Less(q), ShouldBeFalse) 157 158 q = q.Order("turanga") 159 160 So(q.Less(q1), ShouldBeFalse) 161 So(q1.Less(q), ShouldBeTrue) 162 163 q = q.ClearOrder().Order("conrad", "turanga") 164 165 So(q.Less(q1), ShouldBeFalse) 166 So(q1.Less(q), ShouldBeTrue) 167 }) 168 Convey("Compare projection", func() { 169 q := NewQuery("Foo") 170 q1 := NewQuery("Foo").Project("conrad") 171 172 // [] vs ["conrad"] 173 So(q.Less(q1), ShouldBeTrue) 174 So(q1.Less(q), ShouldBeFalse) 175 176 q = q.Project("turanga") 177 178 // ["turanga"] vs ["conrad"] 179 So(q.Less(q1), ShouldBeFalse) 180 So(q1.Less(q), ShouldBeTrue) 181 182 q1 = q1.Project("turanga") 183 184 // ["turanga"] vs ["conrad", "turanga"] 185 So(q.Less(q1), ShouldBeTrue) 186 So(q1.Less(q), ShouldBeFalse) 187 188 q = q.Project("zoidberg") 189 190 // ["turanga", "zoidberg"] vs ["conrad", "turanga"] 191 So(q.Less(q1), ShouldBeFalse) 192 So(q1.Less(q), ShouldBeTrue) 193 }) 194 Convey("Compare equality", func() { 195 // "... turanga == 10 ..." compare "... turanga == 24 ..." 196 q := NewQuery("Foo").Eq("turanga", "10") 197 q1 := NewQuery("Foo").Eq("turanga", "24") 198 199 So(q.Less(q1), ShouldBeTrue) 200 So(q1.Less(q), ShouldBeFalse) 201 202 // "... turanga == 10 ..." compare "... turanga == 10 ..." 203 q = NewQuery("Foo").Eq("turanga", "10") 204 q1 = NewQuery("Foo").Eq("turanga", "10") 205 206 So(q.Less(q1), ShouldBeFalse) 207 So(q1.Less(q), ShouldBeFalse) 208 209 // "... turanga == 10 ..." compare "... zoidberg == 10 ..." 210 q = NewQuery("Foo").Eq("turanga", "10") 211 q1 = NewQuery("Foo").Eq("zoidberg", "10") 212 213 So(q.Less(q1), ShouldBeFalse) 214 So(q1.Less(q), ShouldBeTrue) 215 216 // "... turanga == 10 && turanga == 20 ..." compare "... turanga == 10 ..." 217 q = NewQuery("Foo").Eq("turanga", "10", "20") 218 q1 = NewQuery("Foo").Eq("turanga", "10") 219 220 So(q.Less(q1), ShouldBeFalse) 221 So(q1.Less(q), ShouldBeTrue) 222 223 // "... turanga == 10 ..." compare "..." 224 q = NewQuery("Foo").Eq("turanga", "10") 225 q1 = NewQuery("Foo") 226 227 So(q.Less(q1), ShouldBeFalse) 228 So(q1.Less(q), ShouldBeTrue) 229 }) 230 Convey("Compare In filter", func() { 231 // Equal. Order of filter and values doesn't matter. 232 q1 := NewQuery("Foo").In("p2", "x", "y").In("p1", "a", "b") 233 q2 := NewQuery("Foo").In("p1", "a", "b").In("p2", "y", "x") 234 So(q1.Less(q2), ShouldBeFalse) 235 So(q2.Less(q1), ShouldBeFalse) 236 237 q1 = NewQuery("Foo").In("prop", "a") 238 q2 = NewQuery("Foo").In("prop", "b") 239 So(q1.Less(q2), ShouldBeTrue) 240 So(q2.Less(q1), ShouldBeFalse) 241 242 q1 = NewQuery("Foo").In("prop", "a") 243 q2 = NewQuery("Foo").In("prop", "a", "b") 244 So(q1.Less(q2), ShouldBeTrue) 245 So(q2.Less(q1), ShouldBeFalse) 246 247 q1 = NewQuery("Foo").In("prop", "a") 248 q2 = NewQuery("Foo").In("prop", "a").In("prop", "b") 249 So(q1.Less(q2), ShouldBeTrue) 250 So(q2.Less(q1), ShouldBeFalse) 251 252 q1 = NewQuery("Foo").In("prop", "a") 253 q2 = NewQuery("Foo").In("prop", "a").In("another", "a") 254 So(q1.Less(q2), ShouldBeTrue) 255 So(q2.Less(q1), ShouldBeFalse) 256 }) 257 Convey("Compare inequality", func() { 258 // "... turanga < 10 ..." compare "... turanga < 24 ..." 259 q := NewQuery("Foo").Lt("turanga", "10") 260 q1 := NewQuery("Foo").Lt("turanga", "24") 261 262 So(q.Less(q1), ShouldBeTrue) 263 So(q1.Less(q), ShouldBeFalse) 264 265 // "... turanga < 10 ..." compare "... turanga < 10 ..." 266 q = NewQuery("Foo").Lt("turanga", "10") 267 q1 = NewQuery("Foo").Lt("turanga", "10") 268 269 So(q.Less(q1), ShouldBeFalse) 270 So(q1.Less(q), ShouldBeFalse) 271 272 // "... turanga < 10 ..." compare "... zoidberg < 10 ..." 273 q = NewQuery("Foo").Lt("turanga", "10") 274 q1 = NewQuery("Foo").Lt("zoidberg", "10") 275 276 So(q.Less(q1), ShouldBeTrue) 277 So(q1.Less(q), ShouldBeFalse) 278 279 // "... turanga <= 10 ..." compare "... turanga <= 24 ..." 280 q = NewQuery("Foo").Lte("turanga", "10") 281 q1 = NewQuery("Foo").Lte("turanga", "24") 282 283 So(q.Less(q1), ShouldBeTrue) 284 So(q1.Less(q), ShouldBeFalse) 285 286 // "... turanga <= 10 ..." compare "... turanga <= 10 ..." 287 q = NewQuery("Foo").Lte("turanga", "10") 288 q1 = NewQuery("Foo").Lte("turanga", "10") 289 290 So(q.Less(q1), ShouldBeFalse) 291 So(q1.Less(q), ShouldBeFalse) 292 293 // "... turanga <= 10 ..." compare "... zoidberg <= 10 ..." 294 q = NewQuery("Foo").Lte("turanga", "10") 295 q1 = NewQuery("Foo").Lte("zoidberg", "10") 296 297 So(q.Less(q1), ShouldBeTrue) 298 So(q1.Less(q), ShouldBeFalse) 299 300 // "... turanga < 10 ..." compare "... turanga <= 10 ..." 301 q = NewQuery("Foo").Lt("turanga", "10") 302 q1 = NewQuery("Foo").Lte("turanga", "10") 303 304 So(q.Less(q1), ShouldBeTrue) 305 So(q1.Less(q), ShouldBeFalse) 306 307 // "... turanga < 10 ..." compare "... turanga > 10 ..." 308 q = NewQuery("Foo").Lt("turanga", "10") 309 q1 = NewQuery("Foo").Gt("turanga", "10") 310 311 So(q.Less(q1), ShouldBeTrue) 312 So(q1.Less(q), ShouldBeFalse) 313 314 // "... turanga <= 10 ..." compare "... turanga >= 10 ..." 315 q = NewQuery("Foo").Lte("turanga", "10") 316 q1 = NewQuery("Foo").Gte("turanga", "10") 317 318 So(q.Less(q1), ShouldBeTrue) 319 So(q1.Less(q), ShouldBeFalse) 320 321 // "... turanga > 10 ..." compare "... turanga > 24 ..." 322 q = NewQuery("Foo").Gt("turanga", "10") 323 q1 = NewQuery("Foo").Gt("turanga", "24") 324 325 So(q.Less(q1), ShouldBeTrue) 326 So(q1.Less(q), ShouldBeFalse) 327 328 // "... turanga > 10 ..." compare "... turanga > 10 ..." 329 q = NewQuery("Foo").Gt("turanga", "10") 330 q1 = NewQuery("Foo").Gt("turanga", "10") 331 332 So(q.Less(q1), ShouldBeFalse) 333 So(q1.Less(q), ShouldBeFalse) 334 335 // "... turanga > 10 ..." compare "... zoidberg > 10 ..." 336 q = NewQuery("Foo").Gt("turanga", "10") 337 q1 = NewQuery("Foo").Gt("zoidberg", "10") 338 339 So(q.Less(q1), ShouldBeTrue) 340 So(q1.Less(q), ShouldBeFalse) 341 342 // "... turanga >= 10 ..." compare "... zoidberg >= 24 ..." 343 q = NewQuery("Foo").Gte("turanga", "10") 344 q1 = NewQuery("Foo").Gte("turanga", "24") 345 346 So(q.Less(q1), ShouldBeTrue) 347 So(q1.Less(q), ShouldBeFalse) 348 349 // "... turanga >= 10 ..." compare "... turanga >= 10 ..." 350 q = NewQuery("Foo").Gte("turanga", "10") 351 q1 = NewQuery("Foo").Gte("turanga", "10") 352 353 So(q.Less(q1), ShouldBeFalse) 354 So(q1.Less(q), ShouldBeFalse) 355 356 // "... turanga >= 10 ..." compare "... zoidberg >= 10 ..." 357 q = NewQuery("Foo").Gte("turanga", "10") 358 q1 = NewQuery("Foo").Gte("zoidberg", "10") 359 360 So(q.Less(q1), ShouldBeTrue) 361 So(q1.Less(q), ShouldBeFalse) 362 363 // "... turanga > 10 ..." compare "... turanga >= 10 ..." 364 q = NewQuery("Foo").Gt("turanga", "10") 365 q1 = NewQuery("Foo").Gte("turanga", "10") 366 367 So(q.Less(q1), ShouldBeTrue) 368 So(q1.Less(q), ShouldBeFalse) 369 }) 370 371 Convey("Composite comparison", func() { 372 a := NewQuery("Foo").Project("conrad").Distinct(false) 373 b := NewQuery("Foo").Project("conrad").Distinct(true) 374 So(a.Less(b), ShouldBeTrue) 375 So(b.Less(a), ShouldBeFalse) 376 377 a = NewQuery("Foo").Eq("Prop", "val").Offset(100) 378 b = NewQuery("Foo").Eq("Prop", "val").Offset(200) 379 So(a.Less(b), ShouldBeTrue) 380 So(b.Less(a), ShouldBeFalse) 381 }) 382 }) 383 } 384 385 type queryTest struct { 386 // name is the name of the test case 387 name string 388 389 // q is the input query 390 q *Query 391 392 // gql is the expected generated GQL. 393 gql string 394 395 // assertion is the error to expect after prepping the query, or nil if the 396 // error should be nil. 397 assertion func(err error) 398 399 // equivalentQuery is another query which ShouldResemble q. This is useful to 400 // see the effects of redundancy pruning on e.g. filters. 401 equivalentQuery *Query 402 } 403 404 func nq(kinds ...string) *Query { 405 kind := "Foo" 406 if len(kinds) > 0 { 407 kind = kinds[0] 408 } 409 return NewQuery(kind) 410 } 411 412 func mkKey(elems ...any) *Key { 413 return MkKeyContext("s~aid", "ns").MakeKey(elems...) 414 } 415 416 func errString(v string) func(error) { 417 return func(err error) { 418 So(err, ShouldErrLike, v) 419 } 420 } 421 422 func shouldBeErrInvalidKey(err error) { 423 So(IsErrInvalidKey(err), ShouldBeTrue) 424 } 425 426 var queryTests = []queryTest{ 427 {"only one inequality", 428 nq().Order("bob", "wat").Gt("bob", 10).Lt("wat", 29), 429 "", 430 errString("inequality filters on multiple properties"), 431 nil}, 432 433 {"bad order", 434 nq().Order("+Bob"), 435 "", 436 errString("invalid order"), nil}, 437 438 {"empty order", 439 nq().Order(""), 440 "", 441 errString("empty order"), nil}, 442 443 {"negative offset disables Offset", 444 nq().Offset(100).Offset(-20), 445 "SELECT * FROM `Foo` ORDER BY `__key__`", 446 nil, nq()}, 447 448 {"projecting a keys-only query", 449 nq().Project("hello").KeysOnly(true), 450 "", 451 errString("cannot project a keysOnly query"), nil}, 452 453 {"projecting a keys-only query (reverse)", 454 nq().KeysOnly(true).Project("hello"), 455 "", 456 errString("cannot project a keysOnly query"), nil}, 457 458 {"projecting an empty field", 459 nq().Project("hello", ""), 460 "", 461 errString("cannot filter/project on: \"\""), nil}, 462 463 {"projecting __key__", 464 nq().Project("hello", "__key__"), 465 "", 466 errString("cannot project on \"__key__\""), nil}, 467 468 {"getting all the keys", 469 nq("").KeysOnly(true), 470 "SELECT __key__ ORDER BY `__key__`", 471 nil, nil}, 472 473 {"projecting a duplicate", 474 nq().Project("hello", "hello"), 475 "SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`", 476 nil, nq().Project("hello")}, 477 478 {"projecting a duplicate (style 2)", 479 nq().Project("hello").Project("hello"), 480 "SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`", 481 nil, nq().Project("hello")}, 482 483 {"project distinct", 484 nq().Project("hello").Distinct(true), 485 "SELECT DISTINCT `hello` FROM `Foo` ORDER BY `hello`, `__key__`", 486 nil, nil}, 487 488 {"projecting in an inequality query", 489 nq().Gte("foo", 10).Project("bar").Distinct(true), 490 "SELECT DISTINCT `bar`, `foo` FROM `Foo` WHERE `foo` >= 10 ORDER BY `foo`, `bar`, `__key__`", 491 nil, nil}, 492 493 {"bad ancestors", 494 nq().Ancestor(mkKey("goop", 0)), 495 "", 496 shouldBeErrInvalidKey, nil}, 497 498 {"nil ancestors", 499 nq().Ancestor(nil), 500 "SELECT * FROM `Foo` ORDER BY `__key__`", 501 nil, nq()}, 502 503 {"Bad key filters", 504 nq().Gt("__key__", mkKey("goop", 0)), 505 "", 506 shouldBeErrInvalidKey, nil}, 507 508 {"filters for __key__ that aren't keys", 509 nq().Gt("__key__", 10), 510 "", 511 errString("filters on \"__key__\" must have type *Key"), nil}, 512 513 {"multiple inequalities", 514 nq().Gt("bob", 19).Lt("charlie", 20), 515 "", 516 errString("inequality filters on multiple properties"), nil}, 517 518 {"inequality must be first sort order", 519 nq().Gt("bob", 19).Order("-charlie"), 520 "", 521 errString("first sort order"), nil}, 522 523 {"inequality must be first sort order (reverse)", 524 nq().Order("-charlie").Gt("bob", 19), 525 "", 526 errString("first sort order"), nil}, 527 528 {"equality filter projected field", 529 nq().Project("foo").Eq("foo", 10), 530 "", 531 errString("cannot project"), nil}, 532 533 {"equality filter projected field (reverse)", 534 nq().Eq("foo", 10).Project("foo"), 535 "", 536 errString("cannot project"), nil}, 537 538 {"equality filter projected field (in)", 539 nq().Project("foo").In("foo", 10), 540 "", 541 errString("cannot project"), nil}, 542 543 {"kindless with non-__key__ filters", 544 nq("").Lt("face", 25.3), 545 "", 546 errString("kindless queries can only filter on __key__"), nil}, 547 548 {"kindless with non-__key__ orders", 549 nq("").Order("face"), 550 "", 551 errString("invalid order for kindless query"), nil}, 552 553 {"kindless with descending-__key__ order", 554 nq("").Order("-__key__"), 555 "", 556 errString("invalid order for kindless query"), nil}, 557 558 {"kindless with equality filters", 559 nq("").Eq("hello", 1), 560 "", 561 errString("may not have any equality"), nil}, 562 563 {"kindless with equality filters (in)", 564 nq("").In("hello", 1), 565 "", 566 errString("may not have any equality"), nil}, 567 568 {"kindless with ancestor filter", 569 nq("").Ancestor(mkKey("Parent", 1)), 570 "SELECT * WHERE __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1) ORDER BY `__key__`", 571 nil, nil}, 572 573 {"kindless with ancestor filter and __key__ ineq", 574 nq("").Ancestor(mkKey("Parent", 1)).Lt("__key__", mkKey("Parent", 1, "Sub", "hat")), 575 "SELECT * WHERE `__key__` < KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1, \"Sub\", \"hat\") AND __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1) ORDER BY `__key__`", 576 nil, nil}, 577 578 {"distinct non-projection", 579 nq().Distinct(true).Gt("marla", 1), 580 "SELECT * FROM `Foo` WHERE `marla` > 1 ORDER BY `marla`, `__key__`", 581 nil, nq().Gt("marla", 1)}, 582 583 {"chained errors return the first", 584 nq().Eq("__reserved__", 100).Eq("hello", "wurld").Order(""), 585 "", 586 errString("__reserved__"), nil}, 587 588 {"multiple ancestors", 589 nq().Ancestor(mkKey("something", "correct")).Ancestor(mkKey("something", "else")), 590 ("SELECT * FROM `Foo` " + 591 "WHERE __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"something\", \"else\") " + 592 "ORDER BY `__key__`"), 593 nil, nq().Ancestor(mkKey("something", "else"))}, 594 595 {"filter with illegal type", 596 nq().Eq("something", complex(1, 2)), 597 "", 598 errString("bad type complex"), nil}, 599 600 {"filter with non-comparable type", 601 nq().Eq("something", PropertyMap{}), 602 "", 603 errString("a non-comparable value"), nil}, 604 605 {"sort orders used for equality are ignored", 606 nq().Order("a", "b", "c").Eq("b", 2, 2), 607 "SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`", 608 nil, nq().Order("a", "c").Eq("b", 2)}, 609 610 {"sort orders used for equality are ignored (reversed)", 611 nq().Eq("b", 2).Order("a", "b", "c"), 612 "SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`", 613 nil, 614 nq().Order("a", "c").Eq("b", 2)}, 615 616 {"duplicate equality filters are ignored", 617 nq().Eq("b", 10, -1, 2, 2, 7, 1, 2, 10, -1, 7, 1, 2), 618 "SELECT * FROM `Foo` WHERE `b` = -1 AND `b` = 1 AND `b` = 2 AND `b` = 7 AND `b` = 10 ORDER BY `__key__`", 619 nil, 620 nq().Eq("b", -1, 1, 2, 7, 10)}, 621 622 {"duplicate orders are ignored", 623 nq().Order("a").Order("a").Order("a"), 624 "SELECT * FROM `Foo` ORDER BY `a`, `__key__`", 625 nil, 626 nq().Order("a")}, 627 628 {"Filtering on a reserved property is forbidden", 629 nq().Gte("__special__", 10), 630 "", 631 errString("cannot filter/project on reserved property: \"__special__\""), 632 nil}, 633 634 {"in-bound key filters with ancestor OK", 635 nq().Ancestor(mkKey("Hello", 10)).Lte("__key__", mkKey("Hello", 10, "Something", "hi")), 636 ("SELECT * FROM `Foo` " + 637 "WHERE `__key__` <= KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10, \"Something\", \"hi\") AND " + 638 "__key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10) " + 639 "ORDER BY `__key__`"), 640 nil, 641 nil}, 642 643 {"projection elements get filled in", 644 nq().Project("Foo", "Bar").Order("-Bar"), 645 "SELECT `Bar`, `Foo` FROM `Foo` ORDER BY `Bar` DESC, `Foo`, `__key__`", 646 nil, nq().Project("Foo", "Bar").Order("-Bar").Order("Foo")}, 647 648 {"query without anything is fine", 649 nq(), 650 "SELECT * FROM `Foo` ORDER BY `__key__`", 651 nil, 652 nil}, 653 654 {"ineq on __key__ with ancestor must be an ancestor of __ancestor__!", 655 nq().Ancestor(mkKey("Hello", 10)).Lt("__key__", mkKey("Hello", 8)), 656 "", 657 errString("inequality filters on __key__ must be descendants of the __ancestor__"), 658 nil}, 659 660 {"ineq on __key__ with ancestor must be an ancestor of __ancestor__! (2)", 661 nq().Ancestor(mkKey("Hello", 10)).Gt("__key__", mkKey("Hello", 8)), 662 "", 663 errString("inequality filters on __key__ must be descendants of the __ancestor__"), 664 nil}, 665 666 {"can build an empty query", 667 nq().Lt("hello", 10).Gt("hello", 50), 668 "", 669 func(err error) { So(err, ShouldEqual, ErrNullQuery) }, 670 nil}, 671 672 {"can build an empty query (in)", 673 nq().In("prop"), 674 "", 675 func(err error) { So(err, ShouldEqual, ErrNullQuery) }, 676 nil}, 677 678 {"reject pseudofield $id", 679 nq().Lt("$id", 10), 680 "", 681 func(err error) { So(err, ShouldErrLike, "rejecting field") }, 682 nil}, 683 684 {"IN filters", 685 nq().In("str", "a", "b").In("str", "c").In("int", 1, 2), 686 "SELECT * FROM `Foo` WHERE `int` IN ARRAY(1, 2) AND `str` IN ARRAY(\"a\", \"b\") AND `str` IN ARRAY(\"c\") ORDER BY `__key__`", 687 nil, nil}, 688 } 689 690 func TestQueries(t *testing.T) { 691 t.Parallel() 692 693 Convey("queries have tons of condition checking", t, func() { 694 for _, tc := range queryTests { 695 Convey(tc.name, func() { 696 fq, err := tc.q.Finalize() 697 if err == nil { 698 err = fq.Valid(MkKeyContext("s~aid", "ns")) 699 } 700 if tc.assertion != nil { 701 tc.assertion(err) 702 } else { 703 So(err, ShouldBeNil) 704 } 705 706 if tc.gql != "" { 707 So(fq.GQL(), ShouldEqual, tc.gql) 708 } 709 710 if tc.equivalentQuery != nil { 711 fq2, err := tc.equivalentQuery.Finalize() 712 So(err, ShouldBeNil) 713 714 fq.original = nil 715 fq2.original = nil 716 So(fq, ShouldResemble, fq2) 717 } 718 }) 719 } 720 }) 721 } 722 723 func TestQueryConcurrencySafety(t *testing.T) { 724 t.Parallel() 725 726 Convey("query and derivative query finalization is goroutine-safe", t, func() { 727 const rounds = 10 728 729 q := NewQuery("Foo") 730 731 err := parallel.FanOutIn(func(outerC chan<- func() error) { 732 733 for i := 0; i < rounds; i++ { 734 outerQ := q.Gt("Field", i) 735 736 // Finalize the original query. 737 outerC <- func() error { 738 _, err := q.Finalize() 739 return err 740 } 741 742 // Finalize the derivative query a lot. 743 outerC <- func() error { 744 return parallel.FanOutIn(func(innerC chan<- func() error) { 745 for i := 0; i < rounds; i++ { 746 innerC <- func() error { 747 _, err := outerQ.Finalize() 748 return err 749 } 750 } 751 }) 752 } 753 } 754 }) 755 So(err, ShouldBeNil) 756 }) 757 }