OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package txnBuf |
| 6 |
| 7 import ( |
| 8 "bytes" |
| 9 "fmt" |
| 10 "math/rand" |
| 11 "testing" |
| 12 |
| 13 "github.com/luci/gae/filter/count" |
| 14 "github.com/luci/gae/impl/memory" |
| 15 "github.com/luci/gae/service/datastore" |
| 16 "github.com/luci/luci-go/common/cmpbin" |
| 17 "github.com/luci/luci-go/common/errors" |
| 18 . "github.com/luci/luci-go/common/testing/assertions" |
| 19 . "github.com/smartystreets/goconvey/convey" |
| 20 "golang.org/x/net/context" |
| 21 ) |
| 22 |
| 23 type Foo struct { |
| 24 ID int64 `gae:"$id"` |
| 25 Parent *datastore.Key `gae:"$parent"` |
| 26 |
| 27 Value []int64 |
| 28 ValueNI []byte `gae:",noindex"` |
| 29 } |
| 30 |
| 31 func toIntSlice(stuff []interface{}) []int64 { |
| 32 vals, ok := stuff[0].([]int64) |
| 33 if !ok { |
| 34 vals = make([]int64, len(stuff)) |
| 35 for i := range vals { |
| 36 vals[i] = int64(stuff[i].(int)) |
| 37 } |
| 38 } |
| 39 return vals |
| 40 } |
| 41 |
| 42 func toInt64(thing interface{}) int64 { |
| 43 switch x := thing.(type) { |
| 44 case int: |
| 45 return int64(x) |
| 46 case int64: |
| 47 return x |
| 48 default: |
| 49 panic(fmt.Errorf("wat r it? %v", x)) |
| 50 } |
| 51 } |
| 52 |
| 53 func fooShouldHave(ds datastore.Interface) func(interface{}, ...interface{}) str
ing { |
| 54 return func(id interface{}, values ...interface{}) string { |
| 55 f := &Foo{ID: toInt64(id)} |
| 56 err := ds.Get(f) |
| 57 if len(values) == 0 { |
| 58 return ShouldEqual(err, datastore.ErrNoSuchEntity) |
| 59 } |
| 60 |
| 61 ret := ShouldBeNil(err) |
| 62 if ret == "" { |
| 63 if data, ok := values[0].([]byte); ok { |
| 64 ret = ShouldResemble(f.ValueNI, data) |
| 65 } else { |
| 66 ret = ShouldResemble(f.Value, toIntSlice(values)
) |
| 67 } |
| 68 } |
| 69 return ret |
| 70 } |
| 71 } |
| 72 |
| 73 func fooSetTo(ds datastore.Interface) func(interface{}, ...interface{}) string { |
| 74 return func(id interface{}, values ...interface{}) string { |
| 75 f := &Foo{ID: toInt64(id)} |
| 76 if len(values) == 0 { |
| 77 return ShouldBeNil(ds.Delete(ds.KeyForObj(f))) |
| 78 } |
| 79 if data, ok := values[0].([]byte); ok { |
| 80 f.ValueNI = data |
| 81 } else { |
| 82 f.Value = toIntSlice(values) |
| 83 } |
| 84 return ShouldBeNil(ds.Put(f)) |
| 85 } |
| 86 } |
| 87 |
| 88 func TestTransactionBuffers(t *testing.T) { |
| 89 t.Parallel() |
| 90 |
| 91 cb := func(i int64) string { |
| 92 buf := &bytes.Buffer{} |
| 93 cmpbin.WriteInt(buf, i) |
| 94 return buf.String() |
| 95 } |
| 96 |
| 97 rs := rand.NewSource(0) |
| 98 dataMultiRoot := make([]*Foo, 20) |
| 99 dataSingleRoot := make([]*Foo, 20) |
| 100 root := datastore.MakeKey("dev~app", "", "Parent", 1) |
| 101 nums := make([]string, 20) |
| 102 for i := range dataMultiRoot { |
| 103 id := int64(i + 1) |
| 104 nums[i] = cb(id) |
| 105 |
| 106 val := make([]int64, rs.Int63()%20) |
| 107 for j := range val { |
| 108 r := rs.Int63() |
| 109 val[j] = r |
| 110 } |
| 111 |
| 112 dataMultiRoot[i] = &Foo{ID: id, Value: val} |
| 113 dataSingleRoot[i] = &Foo{ID: id, Parent: root, Value: val} |
| 114 } |
| 115 |
| 116 hugeField := make([]byte, DefaultSizeBudget/8) |
| 117 for i := range hugeField { |
| 118 hugeField[i] = byte(i) |
| 119 } |
| 120 |
| 121 hugeData := make([]*Foo, 11) |
| 122 for i := range hugeData { |
| 123 hugeData[i] = &Foo{ID: int64(i + 1), ValueNI: hugeField} |
| 124 } |
| 125 |
| 126 mkds := func(data []*Foo) (under, over *count.DSCounter, ds datastore.In
terface) { |
| 127 c := memory.Use(context.Background()) |
| 128 ds = datastore.Get(c) |
| 129 _, err := ds.AllocateIDs(ds.KeyForObj(data[0]), 100) |
| 130 if err != nil { |
| 131 panic(err) |
| 132 } |
| 133 if err := ds.PutMulti(data); err != nil { |
| 134 panic(err) |
| 135 } |
| 136 |
| 137 c, under = count.FilterRDS(c) |
| 138 c = FilterRDS(c) |
| 139 c, over = count.FilterRDS(c) |
| 140 ds = datastore.Get(c) |
| 141 return |
| 142 } |
| 143 |
| 144 Convey("Get/Put/Delete", t, func() { |
| 145 under, over, ds := mkds(dataMultiRoot) |
| 146 |
| 147 So(under.PutMulti.Total(), ShouldEqual, 0) |
| 148 So(over.PutMulti.Total(), ShouldEqual, 0) |
| 149 |
| 150 Convey("Good", func() { |
| 151 Convey("read-only", func() { |
| 152 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 153 ds := datastore.Get(c) |
| 154 |
| 155 So(4, fooShouldHave(ds), dataMultiRoot[3
].Value) |
| 156 return nil |
| 157 }, nil), ShouldBeNil) |
| 158 }) |
| 159 |
| 160 Convey("single-level read/write", func() { |
| 161 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 162 ds := datastore.Get(c) |
| 163 |
| 164 So(4, fooShouldHave(ds), dataMultiRoot[3
].Value) |
| 165 |
| 166 So(4, fooSetTo(ds), 1, 2, 3, 4) |
| 167 |
| 168 So(3, fooSetTo(ds), 1, 2, 3, 4) |
| 169 |
| 170 // look! it remembers :) |
| 171 So(4, fooShouldHave(ds), 1, 2, 3, 4) |
| 172 return nil |
| 173 }, &datastore.TransactionOptions{XG: true}), Sho
uldBeNil) |
| 174 |
| 175 So(under.PutMulti.Total(), ShouldEqual, 1) |
| 176 |
| 177 So(3, fooShouldHave(ds), 1, 2, 3, 4) |
| 178 So(4, fooShouldHave(ds), 1, 2, 3, 4) |
| 179 }) |
| 180 |
| 181 Convey("multi-level read/write", func() { |
| 182 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 183 ds := datastore.Get(c) |
| 184 |
| 185 So(3, fooShouldHave(ds), dataMultiRoot[2
].Value) |
| 186 |
| 187 So(3, fooSetTo(ds), 1, 2, 3, 4) |
| 188 So(7, fooSetTo(ds)) |
| 189 |
| 190 // inner, failing, transaction |
| 191 So(ds.RunInTransaction(func(c context.Co
ntext) error { |
| 192 ds := datastore.Get(c) |
| 193 |
| 194 // we can see stuff written in t
he outer txn |
| 195 So(7, fooShouldHave(ds)) |
| 196 So(3, fooShouldHave(ds), 1, 2, 3
, 4) |
| 197 |
| 198 So(3, fooSetTo(ds), 10, 20, 30,
40) |
| 199 |
| 200 // disaster strikes! |
| 201 return errors.New("whaaaa") |
| 202 }, nil), ShouldErrLike, "whaaaa") |
| 203 |
| 204 So(3, fooShouldHave(ds), 1, 2, 3, 4) |
| 205 |
| 206 // inner, successful, transaction |
| 207 So(ds.RunInTransaction(func(c context.Co
ntext) error { |
| 208 ds := datastore.Get(c) |
| 209 So(3, fooShouldHave(ds), 1, 2, 3
, 4) |
| 210 So(3, fooSetTo(ds), 10, 20, 30,
40) |
| 211 return nil |
| 212 }, nil), ShouldBeNil) |
| 213 |
| 214 // now we see it |
| 215 So(3, fooShouldHave(ds), 10, 20, 30, 40) |
| 216 return nil |
| 217 }, &datastore.TransactionOptions{XG: true}), Sho
uldBeNil) |
| 218 |
| 219 So(under.PutMulti.Total(), ShouldEqual, 1) |
| 220 So(under.DeleteMulti.Total(), ShouldEqual, 1) |
| 221 |
| 222 // 'over' Put operations are amplified because t
he inner transaction |
| 223 // commits go through the 'over' filter on the o
uter transaction. So it's |
| 224 // # Puts + # inner txns. |
| 225 So(over.PutMulti.Total(), ShouldEqual, 5) |
| 226 |
| 227 So(7, fooShouldHave(ds)) |
| 228 So(3, fooShouldHave(ds), 10, 20, 30, 40) |
| 229 }) |
| 230 |
| 231 Convey("can allocate IDs from an inner transaction", fun
c() { |
| 232 nums := []int64{4, 8, 15, 16, 23, 42} |
| 233 k := (*datastore.Key)(nil) |
| 234 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 235 ds := datastore.Get(c) |
| 236 |
| 237 So(ds.RunInTransaction(func(c context.Co
ntext) error { |
| 238 ds := datastore.Get(c) |
| 239 f := &Foo{Value: nums} |
| 240 So(ds.Put(f), ShouldBeNil) |
| 241 k = ds.KeyForObj(f) |
| 242 return nil |
| 243 }, nil), ShouldBeNil) |
| 244 |
| 245 So(k.IntID(), fooShouldHave(ds), nums) |
| 246 |
| 247 return nil |
| 248 }, nil), ShouldBeNil) |
| 249 |
| 250 So(k.IntID(), fooShouldHave(ds), nums) |
| 251 }) |
| 252 |
| 253 Convey("inner txn too big allows outer txn", func() { |
| 254 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 255 ds := datastore.Get(c) |
| 256 |
| 257 So(18, fooSetTo(ds), hugeField) |
| 258 |
| 259 So(ds.RunInTransaction(func(c context.Co
ntext) error { |
| 260 ds := datastore.Get(c) |
| 261 So(ds.PutMulti(hugeData), Should
BeNil) |
| 262 return nil |
| 263 }, nil), ShouldErrLike, ErrTransactionTo
oLarge) |
| 264 |
| 265 return nil |
| 266 }, &datastore.TransactionOptions{XG: true}), Sho
uldBeNil) |
| 267 |
| 268 So(18, fooShouldHave(ds), hugeField) |
| 269 }) |
| 270 |
| 271 }) |
| 272 |
| 273 Convey("Bad", func() { |
| 274 |
| 275 Convey("too many roots", func() { |
| 276 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 277 ds := datastore.Get(c) |
| 278 |
| 279 f := &Foo{ID: 7} |
| 280 So(ds.Get(f), ShouldBeNil) |
| 281 So(f, ShouldResemble, dataMultiRoot[6]) |
| 282 |
| 283 So(ds.RunInTransaction(func(c context.Co
ntext) error { |
| 284 return datastore.Get(c).Get(&Foo
{ID: 6}) |
| 285 }, nil), ShouldErrLike, "too many entity
groups") |
| 286 |
| 287 f.Value = []int64{9} |
| 288 So(ds.Put(f), ShouldBeNil) |
| 289 |
| 290 return nil |
| 291 }, nil), ShouldBeNil) |
| 292 |
| 293 f := &Foo{ID: 7} |
| 294 So(ds.Get(f), ShouldBeNil) |
| 295 So(f.Value, ShouldResemble, []int64{9}) |
| 296 }) |
| 297 |
| 298 Convey("buffered errors never reach the datastore", func
() { |
| 299 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 300 ds := datastore.Get(c) |
| 301 |
| 302 So(ds.Put(&Foo{ID: 1, Value: []int64{1,
2, 3, 4}}), ShouldBeNil) |
| 303 return errors.New("boop") |
| 304 }, nil), ShouldErrLike, "boop") |
| 305 So(under.PutMulti.Total(), ShouldEqual, 0) |
| 306 So(over.PutMulti.Successes(), ShouldEqual, 1) |
| 307 }) |
| 308 |
| 309 }) |
| 310 |
| 311 }) |
| 312 |
| 313 Convey("Queries", t, func() { |
| 314 Convey("Good", func() { |
| 315 q := datastore.NewQuery("Foo").Ancestor(root) |
| 316 |
| 317 Convey("normal", func() { |
| 318 _, _, ds := mkds(dataSingleRoot) |
| 319 ds.Testable().AddIndexes(&datastore.IndexDefinit
ion{ |
| 320 Kind: "Foo", |
| 321 Ancestor: true, |
| 322 SortBy: []datastore.IndexColumn{ |
| 323 {Property: "Value"}, |
| 324 }, |
| 325 }) |
| 326 |
| 327 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 328 ds := datastore.Get(c) |
| 329 |
| 330 q = q.Lt("Value", 400000000000000000) |
| 331 |
| 332 vals := []*Foo{} |
| 333 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 334 So(len(vals), ShouldEqual, 8) |
| 335 |
| 336 f := &Foo{ID: 1, Parent: root} |
| 337 So(ds.Get(f), ShouldBeNil) |
| 338 f.Value = append(f.Value, 100) |
| 339 So(ds.Put(f), ShouldBeNil) |
| 340 |
| 341 // Wowee, zowee, merged queries! |
| 342 vals2 := []*Foo{} |
| 343 So(ds.GetAll(q, &vals2), ShouldBeNil) |
| 344 So(len(vals2), ShouldEqual, 9) |
| 345 So(vals2[0], ShouldResemble, f) |
| 346 |
| 347 vals2 = []*Foo{} |
| 348 So(ds.GetAll(q.Limit(2).Offset(1), &vals
2), ShouldBeNil) |
| 349 So(len(vals2), ShouldEqual, 2) |
| 350 So(vals2, ShouldResemble, vals[:2]) |
| 351 |
| 352 return nil |
| 353 }, nil), ShouldBeNil) |
| 354 }) |
| 355 |
| 356 Convey("keysOnly", func() { |
| 357 _, _, ds := mkds([]*Foo{ |
| 358 {ID: 2, Parent: root, Value: []int64{1,
2, 3, 4, 5, 6, 7}}, |
| 359 {ID: 3, Parent: root, Value: []int64{3,
4, 5, 6, 7, 8, 9}}, |
| 360 {ID: 4, Parent: root, Value: []int64{3,
5, 7, 9, 11, 100, 1}}, |
| 361 {ID: 5, Parent: root, Value: []int64{1,
70, 101}}, |
| 362 }) |
| 363 |
| 364 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 365 ds := datastore.Get(c) |
| 366 |
| 367 q = q.Eq("Value", 1).KeysOnly(true) |
| 368 vals := []*datastore.Key{} |
| 369 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 370 So(len(vals), ShouldEqual, 3) |
| 371 So(vals[2], ShouldResemble, ds.MakeKey("
Parent", 1, "Foo", 5)) |
| 372 |
| 373 // can remove keys |
| 374 So(ds.Delete(ds.MakeKey("Parent", 1, "Fo
o", 2)), ShouldBeNil) |
| 375 vals = []*datastore.Key{} |
| 376 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 377 So(len(vals), ShouldEqual, 2) |
| 378 |
| 379 // and add new ones |
| 380 So(ds.Put(&Foo{ID: 1, Parent: root, Valu
e: []int64{1, 7, 100}}), ShouldBeNil) |
| 381 So(ds.Put(&Foo{ID: 7, Parent: root, Valu
e: []int64{20, 1}}), ShouldBeNil) |
| 382 vals = []*datastore.Key{} |
| 383 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 384 So(len(vals), ShouldEqual, 4) |
| 385 |
| 386 So(vals[0].IntID(), ShouldEqual, 1) |
| 387 So(vals[1].IntID(), ShouldEqual, 4) |
| 388 So(vals[2].IntID(), ShouldEqual, 5) |
| 389 So(vals[3].IntID(), ShouldEqual, 7) |
| 390 |
| 391 return nil |
| 392 }, nil), ShouldBeNil) |
| 393 }) |
| 394 |
| 395 Convey("project", func() { |
| 396 _, _, ds := mkds([]*Foo{ |
| 397 {ID: 2, Parent: root, Value: []int64{1,
2, 3, 4, 5, 6, 7}}, |
| 398 {ID: 3, Parent: root, Value: []int64{3,
4, 5, 6, 7, 8, 9}}, |
| 399 {ID: 4, Parent: root, Value: []int64{3,
5, 7, 9, 11, 100, 1}}, |
| 400 {ID: 5, Parent: root, Value: []int64{1,
70, 101}}, |
| 401 }) |
| 402 |
| 403 ds.Testable().AddIndexes(&datastore.IndexDefinit
ion{ |
| 404 Kind: "Foo", |
| 405 Ancestor: true, |
| 406 SortBy: []datastore.IndexColumn{ |
| 407 {Property: "Value"}, |
| 408 }, |
| 409 }) |
| 410 |
| 411 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 412 ds := datastore.Get(c) |
| 413 |
| 414 q = q.Project("Value").Offset(4).Limit(1
0) |
| 415 |
| 416 vals := []datastore.PropertyMap{} |
| 417 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 418 So(len(vals), ShouldEqual, 10) |
| 419 |
| 420 expect := []struct { |
| 421 id int64 |
| 422 val int64 |
| 423 }{ |
| 424 {2, 3}, |
| 425 {3, 3}, |
| 426 {4, 3}, |
| 427 {2, 4}, |
| 428 {3, 4}, |
| 429 {2, 5}, |
| 430 {3, 5}, |
| 431 {4, 5}, |
| 432 {2, 6}, |
| 433 {3, 6}, |
| 434 } |
| 435 |
| 436 for i, pm := range vals { |
| 437 So(pm.GetMetaDefault("key", nil)
, ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id)) |
| 438 So(pm["Value"][0].Value(), Shoul
dEqual, expect[i].val) |
| 439 } |
| 440 |
| 441 // should remove 4 entries, but there ar
e plenty more to fill |
| 442 So(ds.Delete(ds.MakeKey("Parent", 1, "Fo
o", 2)), ShouldBeNil) |
| 443 |
| 444 vals = []datastore.PropertyMap{} |
| 445 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 446 So(len(vals), ShouldEqual, 10) |
| 447 |
| 448 expect = []struct { |
| 449 id int64 |
| 450 val int64 |
| 451 }{ |
| 452 // note (3, 3) and (4, 3) are co
rrectly missing because deleting |
| 453 // 2 removed two entries which a
re hidden by the Offset(4). |
| 454 {3, 4}, |
| 455 {3, 5}, |
| 456 {4, 5}, |
| 457 {3, 6}, |
| 458 {3, 7}, |
| 459 {4, 7}, |
| 460 {3, 8}, |
| 461 {3, 9}, |
| 462 {4, 9}, |
| 463 {4, 11}, |
| 464 } |
| 465 |
| 466 for i, pm := range vals { |
| 467 So(pm.GetMetaDefault("key", nil)
, ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id)) |
| 468 So(pm["Value"][0].Value(), Shoul
dEqual, expect[i].val) |
| 469 } |
| 470 |
| 471 So(ds.Put(&Foo{ID: 1, Parent: root, Valu
e: []int64{3, 9}}), ShouldBeNil) |
| 472 |
| 473 vals = []datastore.PropertyMap{} |
| 474 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 475 So(len(vals), ShouldEqual, 10) |
| 476 |
| 477 expect = []struct { |
| 478 id int64 |
| 479 val int64 |
| 480 }{ |
| 481 // 'invisible' {1, 3} entry bump
s the {4, 3} into view. |
| 482 {4, 3}, |
| 483 {3, 4}, |
| 484 {3, 5}, |
| 485 {4, 5}, |
| 486 {3, 6}, |
| 487 {3, 7}, |
| 488 {4, 7}, |
| 489 {3, 8}, |
| 490 {1, 9}, |
| 491 {3, 9}, |
| 492 {4, 9}, |
| 493 } |
| 494 |
| 495 for i, pm := range vals { |
| 496 So(pm.GetMetaDefault("key", nil)
, ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id)) |
| 497 So(pm["Value"][0].Value(), Shoul
dEqual, expect[i].val) |
| 498 } |
| 499 |
| 500 return nil |
| 501 }, nil), ShouldBeNil) |
| 502 |
| 503 }) |
| 504 |
| 505 Convey("project+distinct", func() { |
| 506 _, _, ds := mkds([]*Foo{ |
| 507 {ID: 2, Parent: root, Value: []int64{1,
2, 3, 4, 5, 6, 7}}, |
| 508 {ID: 3, Parent: root, Value: []int64{3,
4, 5, 6, 7, 8, 9}}, |
| 509 {ID: 4, Parent: root, Value: []int64{3,
5, 7, 9, 11, 100, 1}}, |
| 510 {ID: 5, Parent: root, Value: []int64{1,
70, 101}}, |
| 511 }) |
| 512 |
| 513 ds.Testable().AddIndexes(&datastore.IndexDefinit
ion{ |
| 514 Kind: "Foo", |
| 515 Ancestor: true, |
| 516 SortBy: []datastore.IndexColumn{ |
| 517 {Property: "Value"}, |
| 518 }, |
| 519 }) |
| 520 |
| 521 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 522 ds := datastore.Get(c) |
| 523 |
| 524 q = q.Project("Value").Distinct(true) |
| 525 |
| 526 vals := []datastore.PropertyMap{} |
| 527 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 528 So(len(vals), ShouldEqual, 13) |
| 529 |
| 530 expect := []struct { |
| 531 id int64 |
| 532 val int64 |
| 533 }{ |
| 534 {2, 1}, |
| 535 {2, 2}, |
| 536 {2, 3}, |
| 537 {2, 4}, |
| 538 {2, 5}, |
| 539 {2, 6}, |
| 540 {2, 7}, |
| 541 {3, 8}, |
| 542 {3, 9}, |
| 543 {4, 11}, |
| 544 {5, 70}, |
| 545 {4, 100}, |
| 546 {5, 101}, |
| 547 } |
| 548 |
| 549 for i, pm := range vals { |
| 550 So(pm["Value"][0].Value(), Shoul
dEqual, expect[i].val) |
| 551 So(pm.GetMetaDefault("key", nil)
, ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id)) |
| 552 } |
| 553 |
| 554 return nil |
| 555 }, nil), ShouldBeNil) |
| 556 }) |
| 557 |
| 558 Convey("dedup in action", func() { |
| 559 data := []*Foo{ |
| 560 {ID: 2, Parent: root, Value: []int64{1,
2, 3, 4, 5, 6, 7}}, |
| 561 {ID: 3, Parent: root, Value: []int64{3,
4, 5, 6, 7, 8, 9}}, |
| 562 {ID: 4, Parent: root, Value: []int64{3,
5, 7, 9, 11, 100, 1, 2}}, |
| 563 {ID: 5, Parent: root, Value: []int64{1,
70, 101}}, |
| 564 } |
| 565 |
| 566 _, _, ds := mkds(data) |
| 567 |
| 568 q = q.Eq("Value", 2, 3) |
| 569 vals := []*Foo{} |
| 570 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 571 So(len(vals), ShouldEqual, 2) |
| 572 |
| 573 So(ds.RunInTransaction(func(c context.Context) e
rror { |
| 574 ds := datastore.Get(c) |
| 575 |
| 576 vals := []*Foo{} |
| 577 So(ds.GetAll(q, &vals), ShouldBeNil) |
| 578 So(len(vals), ShouldEqual, 2) |
| 579 |
| 580 So(vals[0], ShouldResemble, data[0]) |
| 581 So(vals[1], ShouldResemble, data[2]) |
| 582 |
| 583 return nil |
| 584 }, nil), ShouldBeNil) |
| 585 }) |
| 586 |
| 587 Convey("project+extra orders", nil) |
| 588 |
| 589 Convey("keysOnly+extra orders", nil) |
| 590 |
| 591 Convey("inner reflects outer", nil) |
| 592 |
| 593 Convey("committed inner reflects in outer", nil) |
| 594 |
| 595 }) |
| 596 |
| 597 }) |
| 598 |
| 599 } |
OLD | NEW |