go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/txnBuf/txnbuf_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 txnBuf 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "math/rand" 22 "testing" 23 24 "go.chromium.org/luci/common/data/cmpbin" 25 "go.chromium.org/luci/common/errors" 26 27 "go.chromium.org/luci/gae/filter/count" 28 "go.chromium.org/luci/gae/impl/memory" 29 ds "go.chromium.org/luci/gae/service/datastore" 30 "go.chromium.org/luci/gae/service/info" 31 32 . "github.com/smartystreets/goconvey/convey" 33 . "go.chromium.org/luci/common/testing/assertions" 34 ) 35 36 type Foo struct { 37 ID int64 `gae:"$id"` 38 Parent *ds.Key `gae:"$parent"` 39 40 Value []int64 41 ValueNI []byte `gae:",noindex"` 42 Sort []string 43 } 44 45 func toIntSlice(stuff []any) []int64 { 46 vals, ok := stuff[0].([]int64) 47 if !ok { 48 vals = make([]int64, len(stuff)) 49 for i := range vals { 50 vals[i] = int64(stuff[i].(int)) 51 } 52 } 53 return vals 54 } 55 56 func toInt64(thing any) int64 { 57 switch x := thing.(type) { 58 case int: 59 return int64(x) 60 case int64: 61 return x 62 default: 63 panic(fmt.Errorf("wat r it? %v", x)) 64 } 65 } 66 67 func fooShouldHave(c context.Context) func(any, ...any) string { 68 return func(id any, values ...any) string { 69 f := &Foo{ID: toInt64(id)} 70 err := ds.Get(c, f) 71 if len(values) == 0 { 72 return ShouldEqual(err, ds.ErrNoSuchEntity) 73 } 74 75 ret := ShouldBeNil(err) 76 if ret == "" { 77 if data, ok := values[0].([]byte); ok { 78 ret = ShouldResemble(f.ValueNI, data) 79 } else { 80 ret = ShouldResemble(f.Value, toIntSlice(values)) 81 } 82 } 83 return ret 84 } 85 } 86 87 func fooSetTo(c context.Context) func(any, ...any) string { 88 return func(id any, values ...any) string { 89 f := &Foo{ID: toInt64(id)} 90 if len(values) == 0 { 91 return ShouldBeNil(ds.Delete(c, ds.KeyForObj(c, f))) 92 } 93 if data, ok := values[0].([]byte); ok { 94 f.ValueNI = data 95 } else { 96 f.Value = toIntSlice(values) 97 } 98 return ShouldBeNil(ds.Put(c, f)) 99 } 100 } 101 102 const dataLen = 26 103 104 var ( 105 dataMultiRoot = make([]*Foo, dataLen) 106 dataSingleRoot = make([]*Foo, dataLen) 107 hugeField = make([]byte, DefaultSizeBudget/8) 108 hugeData = make([]*Foo, 11) 109 root = ds.MkKeyContext("something~else", "").MakeKey("Parent", 1) 110 ) 111 112 func init() { 113 cb := func(i int64) string { 114 buf := &bytes.Buffer{} 115 cmpbin.WriteInt(buf, i) 116 return buf.String() 117 } 118 119 rs := rand.NewSource(0) 120 nums := make([]string, dataLen) 121 for i := range dataMultiRoot { 122 id := int64(i + 1) 123 nums[i] = cb(id) 124 125 val := make([]int64, (rs.Int63()%dataLen)+1) 126 for j := range val { 127 r := rs.Int63() 128 val[j] = r 129 } 130 131 dataMultiRoot[i] = &Foo{ID: id, Value: val} 132 dataSingleRoot[i] = &Foo{ID: id, Parent: root, Value: val} 133 } 134 135 for i := range hugeField { 136 hugeField[i] = byte(i) 137 } 138 139 for i := range hugeData { 140 hugeData[i] = &Foo{ID: int64(i + 1), ValueNI: hugeField} 141 } 142 } 143 144 func mkds(data []*Foo) (under, over *count.DSCounter, c context.Context) { 145 c = memory.UseWithAppID(context.Background(), "something~else") 146 147 dataKey := ds.KeyForObj(c, data[0]) 148 if err := ds.AllocateIDs(c, ds.NewIncompleteKeys(c, 100, dataKey.Kind(), dataKey.Parent())); err != nil { 149 panic(err) 150 } 151 if err := ds.Put(c, data); err != nil { 152 panic(err) 153 } 154 155 c, under = count.FilterRDS(c) 156 c = FilterRDS(c) 157 c, over = count.FilterRDS(c) 158 return 159 } 160 161 func TestTransactionBuffers(t *testing.T) { 162 t.Parallel() 163 164 Convey("Get/Put/Delete", t, func() { 165 under, over, c := mkds(dataMultiRoot) 166 ds.GetTestable(c).SetTransactionRetryCount(1) 167 168 So(under.PutMulti.Total(), ShouldEqual, 0) 169 So(over.PutMulti.Total(), ShouldEqual, 0) 170 171 Convey("Good", func() { 172 Convey("read-only", func() { 173 So(ds.RunInTransaction(c, func(c context.Context) error { 174 So(4, fooShouldHave(c), dataMultiRoot[3].Value) 175 return nil 176 }, nil), ShouldBeNil) 177 }) 178 179 Convey("single-level read/write", func() { 180 So(ds.RunInTransaction(c, func(c context.Context) error { 181 So(4, fooShouldHave(c), dataMultiRoot[3].Value) 182 183 So(4, fooSetTo(c), 1, 2, 3, 4) 184 185 So(3, fooSetTo(c), 1, 2, 3, 4) 186 187 // look! it remembers :) 188 So(4, fooShouldHave(c), 1, 2, 3, 4) 189 return nil 190 }, nil), ShouldBeNil) 191 192 // 2 because we are simulating a transaction failure 193 So(under.PutMulti.Total(), ShouldEqual, 2) 194 195 So(3, fooShouldHave(c), 1, 2, 3, 4) 196 So(4, fooShouldHave(c), 1, 2, 3, 4) 197 }) 198 199 Convey("multi-level read/write", func() { 200 So(ds.RunInTransaction(c, func(c context.Context) error { 201 So(3, fooShouldHave(c), dataMultiRoot[2].Value) 202 203 So(3, fooSetTo(c), 1, 2, 3, 4) 204 So(7, fooSetTo(c)) 205 206 vals := []*Foo{ 207 {ID: 793}, 208 {ID: 7}, 209 {ID: 3}, 210 {ID: 4}, 211 } 212 So(ds.Get(c, vals), ShouldResemble, errors.NewMultiError( 213 ds.ErrNoSuchEntity, 214 ds.ErrNoSuchEntity, 215 nil, 216 nil, 217 )) 218 219 So(vals[0].Value, ShouldBeNil) 220 So(vals[1].Value, ShouldBeNil) 221 So(vals[2].Value, ShouldResemble, []int64{1, 2, 3, 4}) 222 So(vals[3].Value, ShouldResemble, dataSingleRoot[3].Value) 223 224 // inner, failing, transaction 225 So(ds.RunInTransaction(c, func(c context.Context) error { 226 // we can see stuff written in the outer txn 227 So(7, fooShouldHave(c)) 228 So(3, fooShouldHave(c), 1, 2, 3, 4) 229 230 So(3, fooSetTo(c), 10, 20, 30, 40) 231 232 // disaster strikes! 233 return errors.New("whaaaa") 234 }, nil), ShouldErrLike, "whaaaa") 235 236 So(3, fooShouldHave(c), 1, 2, 3, 4) 237 238 // inner, successful, transaction 239 So(ds.RunInTransaction(c, func(c context.Context) error { 240 So(3, fooShouldHave(c), 1, 2, 3, 4) 241 So(3, fooSetTo(c), 10, 20, 30, 40) 242 return nil 243 }, nil), ShouldBeNil) 244 245 // now we see it 246 So(3, fooShouldHave(c), 10, 20, 30, 40) 247 return nil 248 }, nil), ShouldBeNil) 249 250 // 2 because we are simulating a transaction failure 251 So(under.PutMulti.Total(), ShouldEqual, 2) 252 So(under.DeleteMulti.Total(), ShouldEqual, 2) 253 254 So(over.PutMulti.Total(), ShouldEqual, 8) 255 256 So(7, fooShouldHave(c)) 257 So(3, fooShouldHave(c), 10, 20, 30, 40) 258 }) 259 260 Convey("can allocate IDs from an inner transaction", func() { 261 nums := []int64{4, 8, 15, 16, 23, 42} 262 k := (*ds.Key)(nil) 263 So(ds.RunInTransaction(c, func(c context.Context) error { 264 So(ds.RunInTransaction(c, func(c context.Context) error { 265 f := &Foo{Value: nums} 266 So(ds.Put(c, f), ShouldBeNil) 267 k = ds.KeyForObj(c, f) 268 return nil 269 }, nil), ShouldBeNil) 270 271 So(k.IntID(), fooShouldHave(c), nums) 272 273 return nil 274 }, nil), ShouldBeNil) 275 276 So(k.IntID(), fooShouldHave(c), nums) 277 }) 278 279 }) 280 281 Convey("Bad", func() { 282 283 Convey("too many roots", func() { 284 So(ds.RunInTransaction(c, func(c context.Context) error { 285 for i := 1; i < 26; i++ { 286 f := &Foo{ID: int64(i)} 287 So(ds.Get(c, f), ShouldBeNil) 288 So(f, ShouldResemble, dataMultiRoot[i-1]) 289 } 290 291 f := &Foo{ID: 7} 292 f.Value = []int64{9} 293 So(ds.Put(c, f), ShouldBeNil) 294 295 return nil 296 }, nil), ShouldBeNil) 297 298 f := &Foo{ID: 7} 299 So(ds.Get(c, f), ShouldBeNil) 300 So(f.Value, ShouldResemble, []int64{9}) 301 }) 302 303 Convey("buffered errors never reach the datastore", func() { 304 So(ds.RunInTransaction(c, func(c context.Context) error { 305 So(ds.Put(c, &Foo{ID: 1, Value: []int64{1, 2, 3, 4}}), ShouldBeNil) 306 return errors.New("boop") 307 }, nil), ShouldErrLike, "boop") 308 So(under.PutMulti.Total(), ShouldEqual, 0) 309 So(over.PutMulti.Successes(), ShouldEqual, 1) 310 }) 311 312 }) 313 314 }) 315 } 316 317 func TestHuge(t *testing.T) { 318 t.Parallel() 319 320 Convey("testing datastore enforces thresholds", t, func() { 321 _, _, c := mkds(dataMultiRoot) 322 323 Convey("exceeding inner txn size threshold still allows outer", func() { 324 So(ds.RunInTransaction(c, func(c context.Context) error { 325 So(18, fooSetTo(c), hugeField) 326 327 So(ds.RunInTransaction(c, func(c context.Context) error { 328 So(ds.Put(c, hugeData), ShouldBeNil) 329 return nil 330 }, nil), ShouldErrLike, ErrTransactionTooLarge) 331 332 return nil 333 }, nil), ShouldBeNil) 334 335 So(18, fooShouldHave(c), hugeField) 336 }) 337 338 Convey("exceeding inner txn count threshold still allows outer", func() { 339 So(ds.RunInTransaction(c, func(c context.Context) error { 340 So(18, fooSetTo(c), hugeField) 341 342 So(ds.RunInTransaction(c, func(c context.Context) error { 343 p := ds.MakeKey(c, "mom", 1) 344 345 // This will exceed the budget, since we've already done one write in 346 // the parent. 347 for i := 1; i <= DefaultWriteCountBudget; i++ { 348 So(ds.Put(c, &Foo{ID: int64(i), Parent: p}), ShouldBeNil) 349 } 350 return nil 351 }, nil), ShouldErrLike, ErrTransactionTooLarge) 352 353 return nil 354 }, nil), ShouldBeNil) 355 356 So(18, fooShouldHave(c), hugeField) 357 }) 358 359 Convey("exceeding threshold in the parent, then retreating in the child is okay", func() { 360 So(ds.RunInTransaction(c, func(c context.Context) error { 361 So(ds.Put(c, hugeData), ShouldBeNil) 362 So(18, fooSetTo(c), hugeField) 363 364 // We're over threshold! But the child will delete most of this and 365 // bring us back to normal. 366 So(ds.RunInTransaction(c, func(c context.Context) error { 367 keys := make([]*ds.Key, len(hugeData)) 368 for i, d := range hugeData { 369 keys[i] = ds.KeyForObj(c, d) 370 } 371 return ds.Delete(c, keys) 372 }, nil), ShouldBeNil) 373 374 return nil 375 }, nil), ShouldBeNil) 376 377 So(18, fooShouldHave(c), hugeField) 378 }) 379 }) 380 } 381 382 func TestQuerySupport(t *testing.T) { 383 t.Parallel() 384 385 Convey("Queries", t, func() { 386 Convey("Good", func() { 387 q := ds.NewQuery("Foo").Ancestor(root) 388 389 Convey("normal", func() { 390 _, _, c := mkds(dataSingleRoot) 391 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 392 Kind: "Foo", 393 Ancestor: true, 394 SortBy: []ds.IndexColumn{ 395 {Property: "Value"}, 396 }, 397 }) 398 399 So(ds.RunInTransaction(c, func(c context.Context) error { 400 q = q.Lt("Value", 400000000000000000) 401 402 vals := []*Foo{} 403 So(ds.GetAll(c, q, &vals), ShouldBeNil) 404 So(len(vals), ShouldEqual, 15) 405 406 count, err := ds.Count(c, q) 407 So(err, ShouldBeNil) 408 So(count, ShouldEqual, 15) 409 410 f := &Foo{ID: 1, Parent: root} 411 So(ds.Get(c, f), ShouldBeNil) 412 f.Value = append(f.Value, 100) 413 So(ds.Put(c, f), ShouldBeNil) 414 415 // Wowee, zowee, merged queries! 416 vals2 := []*Foo{} 417 So(ds.GetAll(c, q, &vals2), ShouldBeNil) 418 So(len(vals2), ShouldEqual, 16) 419 So(vals2[0], ShouldResemble, f) 420 421 vals2 = []*Foo{} 422 So(ds.GetAll(c, q.Limit(2).Offset(1), &vals2), ShouldBeNil) 423 So(len(vals2), ShouldEqual, 2) 424 So(vals2, ShouldResemble, vals[:2]) 425 426 return nil 427 }, nil), ShouldBeNil) 428 }) 429 430 Convey("keysOnly", func() { 431 _, _, c := mkds([]*Foo{ 432 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}}, 433 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}}, 434 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}}, 435 {ID: 5, Parent: root, Value: []int64{1, 70, 101}}, 436 }) 437 438 So(ds.RunInTransaction(c, func(c context.Context) error { 439 q = q.Eq("Value", 1).KeysOnly(true) 440 vals := []*ds.Key{} 441 So(ds.GetAll(c, q, &vals), ShouldBeNil) 442 So(len(vals), ShouldEqual, 3) 443 So(vals[2], ShouldResemble, ds.MakeKey(c, "Parent", 1, "Foo", 5)) 444 445 // can remove keys 446 So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 2)), ShouldBeNil) 447 vals = []*ds.Key{} 448 So(ds.GetAll(c, q, &vals), ShouldBeNil) 449 So(len(vals), ShouldEqual, 2) 450 451 // and add new ones 452 So(ds.Put(c, &Foo{ID: 1, Parent: root, Value: []int64{1, 7, 100}}), ShouldBeNil) 453 So(ds.Put(c, &Foo{ID: 7, Parent: root, Value: []int64{20, 1}}), ShouldBeNil) 454 vals = []*ds.Key{} 455 So(ds.GetAll(c, q, &vals), ShouldBeNil) 456 So(len(vals), ShouldEqual, 4) 457 458 So(vals[0].IntID(), ShouldEqual, 1) 459 So(vals[1].IntID(), ShouldEqual, 4) 460 So(vals[2].IntID(), ShouldEqual, 5) 461 So(vals[3].IntID(), ShouldEqual, 7) 462 463 return nil 464 }, nil), ShouldBeNil) 465 }) 466 467 Convey("project", func() { 468 _, _, c := mkds([]*Foo{ 469 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}}, 470 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}}, 471 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}}, 472 {ID: 5, Parent: root, Value: []int64{1, 70, 101}}, 473 }) 474 475 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 476 Kind: "Foo", 477 Ancestor: true, 478 SortBy: []ds.IndexColumn{ 479 {Property: "Value"}, 480 }, 481 }) 482 483 So(ds.RunInTransaction(c, func(c context.Context) error { 484 count, err := ds.Count(c, q.Project("Value")) 485 So(err, ShouldBeNil) 486 So(count, ShouldEqual, 24) 487 488 q = q.Project("Value").Offset(4).Limit(10) 489 490 vals := []ds.PropertyMap{} 491 So(ds.GetAll(c, q, &vals), ShouldBeNil) 492 So(len(vals), ShouldEqual, 10) 493 494 expect := []struct { 495 id int64 496 val int64 497 }{ 498 {2, 3}, 499 {3, 3}, 500 {4, 3}, 501 {2, 4}, 502 {3, 4}, 503 {2, 5}, 504 {3, 5}, 505 {4, 5}, 506 {2, 6}, 507 {3, 6}, 508 } 509 510 for i, pm := range vals { 511 So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble, 512 ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id)) 513 So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val) 514 } 515 516 // should remove 4 entries, but there are plenty more to fill 517 So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 2)), ShouldBeNil) 518 519 vals = []ds.PropertyMap{} 520 So(ds.GetAll(c, q, &vals), ShouldBeNil) 521 So(len(vals), ShouldEqual, 10) 522 523 expect = []struct { 524 id int64 525 val int64 526 }{ 527 // note (3, 3) and (4, 3) are correctly missing because deleting 528 // 2 removed two entries which are hidden by the Offset(4). 529 {3, 4}, 530 {3, 5}, 531 {4, 5}, 532 {3, 6}, 533 {3, 7}, 534 {4, 7}, 535 {3, 8}, 536 {3, 9}, 537 {4, 9}, 538 {4, 11}, 539 } 540 541 for i, pm := range vals { 542 So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble, 543 ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id)) 544 So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val) 545 } 546 547 So(ds.Put(c, &Foo{ID: 1, Parent: root, Value: []int64{3, 9}}), ShouldBeNil) 548 549 vals = []ds.PropertyMap{} 550 So(ds.GetAll(c, q, &vals), ShouldBeNil) 551 So(len(vals), ShouldEqual, 10) 552 553 expect = []struct { 554 id int64 555 val int64 556 }{ 557 // 'invisible' {1, 3} entry bumps the {4, 3} into view. 558 {4, 3}, 559 {3, 4}, 560 {3, 5}, 561 {4, 5}, 562 {3, 6}, 563 {3, 7}, 564 {4, 7}, 565 {3, 8}, 566 {1, 9}, 567 {3, 9}, 568 {4, 9}, 569 } 570 571 for i, pm := range vals { 572 So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble, 573 ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id)) 574 So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val) 575 } 576 577 return nil 578 }, nil), ShouldBeNil) 579 580 }) 581 582 Convey("project+distinct", func() { 583 _, _, c := mkds([]*Foo{ 584 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}}, 585 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}}, 586 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}}, 587 {ID: 5, Parent: root, Value: []int64{1, 70, 101}}, 588 }) 589 590 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 591 Kind: "Foo", 592 Ancestor: true, 593 SortBy: []ds.IndexColumn{ 594 {Property: "Value"}, 595 }, 596 }) 597 598 So(ds.RunInTransaction(c, func(c context.Context) error { 599 q = q.Project("Value").Distinct(true) 600 601 vals := []ds.PropertyMap{} 602 So(ds.GetAll(c, q, &vals), ShouldBeNil) 603 So(len(vals), ShouldEqual, 13) 604 605 expect := []struct { 606 id int64 607 val int64 608 }{ 609 {2, 1}, 610 {2, 2}, 611 {2, 3}, 612 {2, 4}, 613 {2, 5}, 614 {2, 6}, 615 {2, 7}, 616 {3, 8}, 617 {3, 9}, 618 {4, 11}, 619 {5, 70}, 620 {4, 100}, 621 {5, 101}, 622 } 623 624 for i, pm := range vals { 625 So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val) 626 So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble, 627 ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id)) 628 } 629 630 return nil 631 }, nil), ShouldBeNil) 632 }) 633 634 Convey("overwrite", func() { 635 data := []*Foo{ 636 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}}, 637 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}}, 638 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1, 2}}, 639 {ID: 5, Parent: root, Value: []int64{1, 70, 101}}, 640 } 641 642 _, _, c := mkds(data) 643 644 q = q.Eq("Value", 2, 3) 645 646 So(ds.RunInTransaction(c, func(c context.Context) error { 647 vals := []*Foo{} 648 So(ds.GetAll(c, q, &vals), ShouldBeNil) 649 So(len(vals), ShouldEqual, 2) 650 651 So(vals[0], ShouldResemble, data[0]) 652 So(vals[1], ShouldResemble, data[2]) 653 654 foo2 := &Foo{ID: 2, Parent: root, Value: []int64{2, 3}} 655 So(ds.Put(c, foo2), ShouldBeNil) 656 657 vals = []*Foo{} 658 So(ds.GetAll(c, q, &vals), ShouldBeNil) 659 So(len(vals), ShouldEqual, 2) 660 661 So(vals[0], ShouldResemble, foo2) 662 So(vals[1], ShouldResemble, data[2]) 663 664 foo1 := &Foo{ID: 1, Parent: root, Value: []int64{2, 3}} 665 So(ds.Put(c, foo1), ShouldBeNil) 666 667 vals = []*Foo{} 668 So(ds.GetAll(c, q, &vals), ShouldBeNil) 669 So(len(vals), ShouldEqual, 3) 670 671 So(vals[0], ShouldResemble, foo1) 672 So(vals[1], ShouldResemble, foo2) 673 So(vals[2], ShouldResemble, data[2]) 674 675 return nil 676 }, nil), ShouldBeNil) 677 }) 678 679 projectData := []*Foo{ 680 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}, Sort: []string{"x", "z"}}, 681 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}, Sort: []string{"b"}}, 682 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1, 2}, Sort: []string{"aa", "a"}}, 683 {ID: 5, Parent: root, Value: []int64{1, 70, 101}, Sort: []string{"c"}}, 684 } 685 686 Convey("project+extra orders", func() { 687 688 _, _, c := mkds(projectData) 689 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 690 Kind: "Foo", 691 Ancestor: true, 692 SortBy: []ds.IndexColumn{ 693 {Property: "Sort", Descending: true}, 694 {Property: "Value", Descending: true}, 695 }, 696 }) 697 698 q = q.Project("Value").Order("-Sort", "-Value").Distinct(true) 699 So(ds.RunInTransaction(c, func(c context.Context) error { 700 So(ds.Put(c, &Foo{ 701 ID: 1, Parent: root, Value: []int64{0, 1, 1000}, 702 Sort: []string{"zz"}}), ShouldBeNil) 703 704 vals := []ds.PropertyMap{} 705 So(ds.GetAll(c, q, &vals), ShouldBeNil) 706 707 expect := []struct { 708 id int64 709 val int64 710 }{ 711 {1, 1000}, 712 {1, 1}, 713 {1, 0}, 714 {2, 7}, 715 {2, 6}, 716 {2, 5}, 717 {2, 4}, 718 {2, 3}, 719 {2, 2}, 720 {5, 101}, 721 {5, 70}, 722 {3, 9}, 723 {3, 8}, 724 {4, 100}, 725 {4, 11}, 726 } 727 728 for i, pm := range vals { 729 So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val) 730 So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble, 731 ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id)) 732 } 733 734 return nil 735 }, nil), ShouldBeNil) 736 }) 737 738 Convey("buffered entity sorts before ineq, but after first parent entity", func() { 739 // If we got this wrong, we'd see Foo,3 come before Foo,2. This might 740 // happen because we calculate the comparison string for each entity 741 // based on the whole entity, but we forgot to limit the comparison 742 // string generation by the inequality criteria. 743 data := []*Foo{ 744 {ID: 2, Parent: root, Value: []int64{2, 3, 5, 6}, Sort: []string{"z"}}, 745 } 746 747 _, _, c := mkds(data) 748 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 749 Kind: "Foo", 750 Ancestor: true, 751 SortBy: []ds.IndexColumn{ 752 {Property: "Value"}, 753 }, 754 }) 755 756 q = q.Gt("Value", 2).Limit(2) 757 758 So(ds.RunInTransaction(c, func(c context.Context) error { 759 foo1 := &Foo{ID: 3, Parent: root, Value: []int64{0, 2, 3, 4}} 760 So(ds.Put(c, foo1), ShouldBeNil) 761 762 vals := []*Foo{} 763 So(ds.GetAll(c, q, &vals), ShouldBeNil) 764 So(len(vals), ShouldEqual, 2) 765 766 So(vals[0], ShouldResemble, data[0]) 767 So(vals[1], ShouldResemble, foo1) 768 769 return nil 770 }, nil), ShouldBeNil) 771 }) 772 773 Convey("keysOnly+extra orders", func() { 774 _, _, c := mkds(projectData) 775 ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{ 776 Kind: "Foo", 777 Ancestor: true, 778 SortBy: []ds.IndexColumn{ 779 {Property: "Sort"}, 780 }, 781 }) 782 783 q = q.Order("Sort").KeysOnly(true) 784 785 So(ds.RunInTransaction(c, func(c context.Context) error { 786 So(ds.Put(c, &Foo{ 787 ID: 1, Parent: root, Value: []int64{0, 1, 1000}, 788 Sort: []string{"x", "zz"}}), ShouldBeNil) 789 790 So(ds.Put(c, &Foo{ 791 ID: 2, Parent: root, Value: []int64{0, 1, 1000}, 792 Sort: []string{"zz", "zzz", "zzzz"}}), ShouldBeNil) 793 794 vals := []*ds.Key{} 795 So(ds.GetAll(c, q, &vals), ShouldBeNil) 796 So(len(vals), ShouldEqual, 5) 797 798 kc := ds.GetKeyContext(c) 799 So(vals, ShouldResemble, []*ds.Key{ 800 kc.MakeKey("Parent", 1, "Foo", 4), 801 kc.MakeKey("Parent", 1, "Foo", 3), 802 kc.MakeKey("Parent", 1, "Foo", 5), 803 kc.MakeKey("Parent", 1, "Foo", 1), 804 kc.MakeKey("Parent", 1, "Foo", 2), 805 }) 806 807 return nil 808 }, nil), ShouldBeNil) 809 }) 810 811 Convey("query accross nested transactions", func() { 812 _, _, c := mkds(projectData) 813 q = q.Eq("Value", 2, 3) 814 815 foo1 := &Foo{ID: 1, Parent: root, Value: []int64{2, 3}} 816 foo7 := &Foo{ID: 7, Parent: root, Value: []int64{2, 3}} 817 818 So(ds.RunInTransaction(c, func(c context.Context) error { 819 So(ds.Put(c, foo1), ShouldBeNil) 820 821 vals := []*Foo{} 822 So(ds.GetAll(c, q, &vals), ShouldBeNil) 823 So(vals, ShouldResemble, []*Foo{foo1, projectData[0], projectData[2]}) 824 825 So(ds.RunInTransaction(c, func(c context.Context) error { 826 vals := []*Foo{} 827 So(ds.GetAll(c, q, &vals), ShouldBeNil) 828 So(vals, ShouldResemble, []*Foo{foo1, projectData[0], projectData[2]}) 829 830 So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 4)), ShouldBeNil) 831 So(ds.Put(c, foo7), ShouldBeNil) 832 833 vals = []*Foo{} 834 So(ds.GetAll(c, q, &vals), ShouldBeNil) 835 So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7}) 836 837 return nil 838 }, nil), ShouldBeNil) 839 840 vals = []*Foo{} 841 So(ds.GetAll(c, q, &vals), ShouldBeNil) 842 So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7}) 843 844 return nil 845 }, nil), ShouldBeNil) 846 847 vals := []*Foo{} 848 So(ds.GetAll(c, q, &vals), ShouldBeNil) 849 So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7}) 850 851 }) 852 853 Convey("start transaction from inside query", func() { 854 _, _, c := mkds(projectData) 855 So(ds.RunInTransaction(c, func(c context.Context) error { 856 q := ds.NewQuery("Foo").Ancestor(root) 857 return ds.Run(c, q, func(pm ds.PropertyMap) { 858 So(ds.RunInTransaction(c, func(c context.Context) error { 859 pm["Value"] = append(pm.Slice("Value"), ds.MkProperty("wat")) 860 return ds.Put(c, pm) 861 }, nil), ShouldBeNil) 862 }) 863 }, nil), ShouldBeNil) 864 865 So(ds.Run(c, ds.NewQuery("Foo"), func(pm ds.PropertyMap) { 866 val := pm.Slice("Value") 867 So(val[len(val)-1].Value(), ShouldResemble, "wat") 868 }), ShouldBeNil) 869 }) 870 871 }) 872 873 }) 874 875 } 876 877 func TestRegressions(t *testing.T) { 878 Convey("Regression tests", t, func() { 879 Convey("can remove namespace from txnBuf filter", func() { 880 c := info.MustNamespace(memory.Use(context.Background()), "foobar") 881 So(info.GetNamespace(c), ShouldEqual, "foobar") 882 ds.RunInTransaction(FilterRDS(c), func(c context.Context) error { 883 So(info.GetNamespace(c), ShouldEqual, "foobar") 884 c = ds.WithoutTransaction(info.MustNamespace(c, "")) 885 So(info.GetNamespace(c), ShouldEqual, "") 886 return nil 887 }, nil) 888 }) 889 }) 890 }